melos 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +19 -0
- data/lib/melos/struct/base.rb +32 -54
- data/lib/melos/struct/ratchet_tree.rb +10 -10
- data/lib/melos/struct/structs.rb +13 -12
- data/lib/melos/vec.rb +23 -20
- data/lib/melos/version.rb +1 -1
- metadata +3 -4
- data/.rspec +0 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 2e29fdc55c828d8bdd2bfd2c2375835dd9e3bf04020bf04291915cd0de2af83b
         | 
| 4 | 
            +
              data.tar.gz: 467c07c1eaff33a00fb9261a579e52a6fed0fd0d15c9cc1f83b15c99b7a98aa4
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 9ace207ff77ecfa0e220fcc94366e2e07cc41b68424bc6840254c4f5c8c3c8ec1d2e1c94bebde387454a2f12c0054ec00031e41bd77b015c0f5a74c085f4ca46
         | 
| 7 | 
            +
              data.tar.gz: 3741b2507119fa79de960ab719f37e447b15521b6387a144a7a70ebeeb5e4a57e604e226847b159854e56a796fa2f3468f548c16d64932fcdd6468ef5b4731b3
         | 
    
        data/README.md
    CHANGED
    
    | @@ -1,5 +1,24 @@ | |
| 1 1 | 
             
            # Melos
         | 
| 2 2 |  | 
| 3 | 
            +
             | 
| 4 | 
            +
            [](https://badge.fury.io/rb/melos)
         | 
| 5 | 
            +
             | 
| 3 6 | 
             
            a [Messaging Layer Security Protocol](https://www.rfc-editor.org/rfc/rfc9420.html) implementation in Ruby
         | 
| 4 7 |  | 
| 5 8 | 
             
            (yes, [an mls gem](https://rubygems.org/gems/mls) happened to exist since 2014, so...)
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            ## Note on implementation status
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            As of version 0.0.1, this implements:
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            - Serialization/deserialization of messages
         | 
| 15 | 
            +
            - Key Schedule
         | 
| 16 | 
            +
            - Encryption Secret Tree
         | 
| 17 | 
            +
            - Applying Add/Update/Remove/PreSharedKey/GroupContextExtensions Proposal types
         | 
| 18 | 
            +
            - (thus passes all test vectors in [mls-implementations/test-vectors.md](https://github.com/mlswg/mls-implementations/blob/main/test-vectors.md))
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            but lacks the following (not a complete list):
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            - Creating messages
         | 
| 23 | 
            +
            - Applying ReInit/ExternalInit proposals
         | 
| 24 | 
            +
            - Validation (rejecting error cases)
         | 
    
        data/lib/melos/struct/base.rb
    CHANGED
    
    | @@ -1,19 +1,13 @@ | |
| 1 1 | 
             
            module Melos::Struct; end
         | 
| 2 2 |  | 
| 3 3 | 
             
            class Melos::Struct::Base
         | 
| 4 | 
            -
              def initialize( | 
| 5 | 
            -
                 | 
| 4 | 
            +
              def initialize(stream)
         | 
| 5 | 
            +
                stream = StringIO.new(stream) if stream.is_a?(String)
         | 
| 6 | 
            +
                context = deserialize(stream)
         | 
| 6 7 | 
             
                set_instance_vars(context)
         | 
| 7 8 | 
             
                self
         | 
| 8 9 | 
             
              end
         | 
| 9 10 |  | 
| 10 | 
            -
              def self.new_and_rest(buf)
         | 
| 11 | 
            -
                instance = self.allocate
         | 
| 12 | 
            -
                context, buf = instance.send(:deserialize, buf)
         | 
| 13 | 
            -
                instance.send(:set_instance_vars, context)
         | 
| 14 | 
            -
                [instance, buf]
         | 
| 15 | 
            -
              end
         | 
| 16 | 
            -
             | 
| 17 11 | 
             
              def raw
         | 
| 18 12 | 
             
                buf = ''
         | 
| 19 13 | 
             
                self.class::STRUCT.each do |elem|
         | 
| @@ -29,45 +23,35 @@ class Melos::Struct::Base | |
| 29 23 | 
             
                buf
         | 
| 30 24 | 
             
              end
         | 
| 31 25 |  | 
| 32 | 
            -
              def self.vecs(buf)
         | 
| 33 | 
            -
                value, buf = Melos::Vec.parse_vec(buf)
         | 
| 34 | 
            -
                array = []
         | 
| 35 | 
            -
                while (value.bytesize > 0)
         | 
| 36 | 
            -
                  current_instance, value = Melos::Vec.parse_vec(value)
         | 
| 37 | 
            -
                  array << current_instance
         | 
| 38 | 
            -
                end
         | 
| 39 | 
            -
                [array, buf]
         | 
| 40 | 
            -
              end
         | 
| 41 | 
            -
             | 
| 42 26 | 
             
              # context here takes a hash
         | 
| 43 | 
            -
              # returns  | 
| 27 | 
            +
              # returns the deserialized value
         | 
| 44 28 | 
             
              # value could return nil, which means predicate was not applicable
         | 
| 45 29 | 
             
              # predicate takes the context and returns true or false
         | 
| 46 | 
            -
              def deserialize_select_elem_with_context( | 
| 30 | 
            +
              def deserialize_select_elem_with_context(stream, context, predicate, type, type_param)
         | 
| 47 31 | 
             
                if predicate.(context)
         | 
| 48 | 
            -
                  deserialize_elem( | 
| 32 | 
            +
                  deserialize_elem(stream, type, type_param)
         | 
| 49 33 | 
             
                else
         | 
| 50 | 
            -
                   | 
| 34 | 
            +
                  nil
         | 
| 51 35 | 
             
                end
         | 
| 52 36 | 
             
              end
         | 
| 53 37 |  | 
| 54 38 | 
             
              private
         | 
| 55 | 
            -
              def deserialize( | 
| 39 | 
            +
              def deserialize(stream)
         | 
| 56 40 | 
             
                context = []
         | 
| 57 41 | 
             
                self.class::STRUCT.each do |elem|
         | 
| 58 42 | 
             
                  case elem[1]
         | 
| 59 43 | 
             
                  when :select
         | 
| 60 | 
            -
                    value | 
| 44 | 
            +
                    value = deserialize_select_elem_with_context(stream, context.to_h, elem[2], elem[3], elem[4])
         | 
| 61 45 | 
             
                    context << [elem[0], value]
         | 
| 62 46 | 
             
                  when :framed_content_auth_data
         | 
| 63 | 
            -
                    value | 
| 47 | 
            +
                    value = Melos::Struct::FramedContentAuthData.new_with_content_type(stream, context.to_h[:content].content_type)
         | 
| 64 48 | 
             
                    context << [elem[0], value]
         | 
| 65 49 | 
             
                  else
         | 
| 66 | 
            -
                    value | 
| 50 | 
            +
                    value = deserialize_elem(stream, elem[1], elem[2])
         | 
| 67 51 | 
             
                    context << [elem[0], value]
         | 
| 68 52 | 
             
                  end
         | 
| 69 53 | 
             
                end
         | 
| 70 | 
            -
                 | 
| 54 | 
            +
                context
         | 
| 71 55 | 
             
              end
         | 
| 72 56 |  | 
| 73 57 | 
             
              def set_instance_vars(context)
         | 
| @@ -76,57 +60,51 @@ class Melos::Struct::Base | |
| 76 60 | 
             
                end
         | 
| 77 61 | 
             
              end
         | 
| 78 62 |  | 
| 79 | 
            -
              def deserialize_elem( | 
| 63 | 
            +
              def deserialize_elem(stream, type, type_param)
         | 
| 80 64 | 
             
                case type
         | 
| 81 65 | 
             
                when :uint8
         | 
| 82 | 
            -
                  value =  | 
| 83 | 
            -
                  buf = buf.byteslice(1..)
         | 
| 66 | 
            +
                  value = stream.read(1).unpack1('C')
         | 
| 84 67 | 
             
                when :uint16
         | 
| 85 | 
            -
                  value =  | 
| 86 | 
            -
                  buf = buf.byteslice(2..)
         | 
| 68 | 
            +
                  value = stream.read(2).unpack1('S>')
         | 
| 87 69 | 
             
                when :uint32
         | 
| 88 | 
            -
                  value =  | 
| 89 | 
            -
                  buf = buf.byteslice(4..)
         | 
| 70 | 
            +
                  value = stream.read(4).unpack1('L>')
         | 
| 90 71 | 
             
                when :uint64
         | 
| 91 | 
            -
                  value =  | 
| 92 | 
            -
                  buf = buf.byteslice(8..)
         | 
| 72 | 
            +
                  value = stream.read(8).unpack1('Q>')
         | 
| 93 73 | 
             
                when :vec
         | 
| 94 | 
            -
                  value | 
| 74 | 
            +
                  value = Melos::Vec.parse_stringio(stream)
         | 
| 95 75 | 
             
                when :vec_of_type
         | 
| 96 | 
            -
                   | 
| 76 | 
            +
                  data = Melos::Vec.parse_stringio(stream)
         | 
| 97 77 | 
             
                  value = []
         | 
| 98 | 
            -
                   | 
| 99 | 
            -
             | 
| 78 | 
            +
                  data_stream = StringIO.new(data)
         | 
| 79 | 
            +
                  while (!data_stream.eof?)
         | 
| 80 | 
            +
                    current_instance = deserialize_elem(data_stream, type_param, nil)
         | 
| 100 81 | 
             
                    value << current_instance
         | 
| 101 82 | 
             
                  end
         | 
| 102 83 | 
             
                when :class
         | 
| 103 | 
            -
                  value | 
| 84 | 
            +
                  value = type_param.send(:new, stream)
         | 
| 104 85 | 
             
                when :classes
         | 
| 105 | 
            -
                   | 
| 106 | 
            -
                  # puts "#{prefix}, #{length}"
         | 
| 107 | 
            -
                  vec, buf = Melos::Vec.parse_vec(buf)
         | 
| 86 | 
            +
                  data = Melos::Vec.parse_stringio(stream)
         | 
| 108 87 | 
             
                  value = []
         | 
| 109 | 
            -
                   | 
| 110 | 
            -
             | 
| 88 | 
            +
                  data_stream = StringIO.new(data)
         | 
| 89 | 
            +
                  while (!data_stream.eof?)
         | 
| 90 | 
            +
                    current_instance = type_param.send(:new, data_stream)
         | 
| 111 91 | 
             
                    value << current_instance
         | 
| 112 92 | 
             
                  end
         | 
| 113 93 | 
             
                when :optional
         | 
| 114 | 
            -
                  presence =  | 
| 115 | 
            -
                  buf = buf.byteslice(1..)
         | 
| 94 | 
            +
                  presence = stream.read(1).unpack1('C')
         | 
| 116 95 | 
             
                  case presence
         | 
| 117 96 | 
             
                  when 0
         | 
| 118 97 | 
             
                    value = nil
         | 
| 119 98 | 
             
                  when 1
         | 
| 120 99 | 
             
                    # as of RFC 9420, optional always takes a class
         | 
| 121 | 
            -
                    value | 
| 100 | 
            +
                    value = type_param.send(:new, stream)
         | 
| 122 101 | 
             
                  end
         | 
| 123 102 | 
             
                when :opaque
         | 
| 124 | 
            -
                  value =  | 
| 125 | 
            -
                  buf = buf.byteslice((type_param.to_i)..)
         | 
| 103 | 
            +
                  value = stream.read(type_param.to_i)
         | 
| 126 104 | 
             
                when :padding
         | 
| 127 | 
            -
                  value =  | 
| 105 | 
            +
                  value = stream.read
         | 
| 128 106 | 
             
                end
         | 
| 129 | 
            -
                 | 
| 107 | 
            +
                value
         | 
| 130 108 | 
             
              end
         | 
| 131 109 |  | 
| 132 110 | 
             
              # take a name and type
         | 
| @@ -6,26 +6,26 @@ require_relative '../tree' | |
| 6 6 | 
             
            require_relative '../crypto'
         | 
| 7 7 |  | 
| 8 8 | 
             
            module Melos::Struct::RatchetTree
         | 
| 9 | 
            -
              def self.parse( | 
| 10 | 
            -
                 | 
| 11 | 
            -
                 | 
| 9 | 
            +
              def self.parse(stream)
         | 
| 10 | 
            +
                stream = StringIO.new(stream) if stream.is_a?(String)
         | 
| 11 | 
            +
                new(stream)
         | 
| 12 12 | 
             
              end
         | 
| 13 13 |  | 
| 14 | 
            -
              def self. | 
| 14 | 
            +
              def self.new(stream)
         | 
| 15 15 | 
             
                array = []
         | 
| 16 | 
            -
                 | 
| 17 | 
            -
                 | 
| 18 | 
            -
             | 
| 19 | 
            -
                   | 
| 16 | 
            +
                data = Melos::Vec.parse_stringio(stream)
         | 
| 17 | 
            +
                data_stream = StringIO.new(data)
         | 
| 18 | 
            +
                while !data_stream.eof?
         | 
| 19 | 
            +
                  presence = data_stream.read(1).unpack1('C')
         | 
| 20 20 | 
             
                  case presence
         | 
| 21 21 | 
             
                  when 0
         | 
| 22 22 | 
             
                    array << nil
         | 
| 23 23 | 
             
                  when 1
         | 
| 24 | 
            -
                    node | 
| 24 | 
            +
                    node = Melos::Struct::Node.new(data_stream)
         | 
| 25 25 | 
             
                    array << node
         | 
| 26 26 | 
             
                  end
         | 
| 27 27 | 
             
                end
         | 
| 28 | 
            -
                 | 
| 28 | 
            +
                array
         | 
| 29 29 | 
             
              end
         | 
| 30 30 |  | 
| 31 31 | 
             
              def self.raw(array)
         | 
    
        data/lib/melos/struct/structs.rb
    CHANGED
    
    | @@ -638,18 +638,18 @@ class Melos::Struct::FramedContentAuthData < Melos::Struct::Base | |
| 638 638 | 
             
              ]
         | 
| 639 639 |  | 
| 640 640 | 
             
              # initialize from stream
         | 
| 641 | 
            -
              def self. | 
| 641 | 
            +
              def self.new_with_content_type(stream, content_type)
         | 
| 642 642 | 
             
                instance = self.allocate
         | 
| 643 | 
            -
                context | 
| 643 | 
            +
                context = instance.send(:deserialize, stream)
         | 
| 644 644 | 
             
                # custom part based on instance variable
         | 
| 645 645 | 
             
                if content_type == Melos::Constants::ContentType::COMMIT # commit
         | 
| 646 646 | 
             
                  # read MAC(opaque <V>) confirmation_tag
         | 
| 647 | 
            -
                  value | 
| 647 | 
            +
                  value = Melos::Vec.parse_stringio(stream)
         | 
| 648 648 | 
             
                  context << [:confirmation_tag, value]
         | 
| 649 649 | 
             
                end
         | 
| 650 650 | 
             
                context << [:content_type, content_type]
         | 
| 651 651 | 
             
                instance.send(:set_instance_vars, context)
         | 
| 652 | 
            -
                 | 
| 652 | 
            +
                instance
         | 
| 653 653 | 
             
              end
         | 
| 654 654 |  | 
| 655 655 | 
             
              def raw
         | 
| @@ -887,7 +887,8 @@ class Melos::Struct::PrivateMessage < Melos::Struct::Base | |
| 887 887 | 
             
                sender_data = decrypt_sender_data(suite, sender_data_secret)
         | 
| 888 888 | 
             
                key, nonce, _ = Melos::SecretTree.ratchet_until_and_get(suite, content_type, secret_tree, sender_data.leaf_index, sender_data.generation)
         | 
| 889 889 | 
             
                new_nonce = self.class.apply_nonce_reuse_guard(nonce, sender_data.reuse_guard)
         | 
| 890 | 
            -
                 | 
| 890 | 
            +
                stream = StringIO.new(Melos::Crypto.aead_decrypt(suite, key, new_nonce, private_content_aad, ciphertext))
         | 
| 891 | 
            +
                pmc = Melos::Struct::PrivateMessageContent.new_with_content_type(stream, content_type)
         | 
| 891 892 |  | 
| 892 893 | 
             
                fc = Melos::Struct::FramedContent.create(
         | 
| 893 894 | 
             
                  group_id: group_id,
         | 
| @@ -945,27 +946,27 @@ class Melos::Struct::PrivateMessageContent < Melos::Struct::Base | |
| 945 946 | 
             
              # bytes -> struct: decode the content and auth field, rest is padding
         | 
| 946 947 | 
             
              # struct -> bytes: encode content and auth field, add set amount of padding (zero bytes)
         | 
| 947 948 |  | 
| 948 | 
            -
              def self. | 
| 949 | 
            +
              def self.new_with_content_type(stream, content_type)
         | 
| 949 950 | 
             
                instance = self.allocate
         | 
| 950 951 | 
             
                context = []
         | 
| 951 952 | 
             
                # deserialize application_data/proposal/commit
         | 
| 952 953 | 
             
                case content_type
         | 
| 953 954 | 
             
                when Melos::Constants::ContentType::APPLICATION
         | 
| 954 | 
            -
                  value | 
| 955 | 
            +
                  value = Melos::Vec.parse_stringio(stream)
         | 
| 955 956 | 
             
                  context << [:application_data, value]
         | 
| 956 957 | 
             
                when Melos::Constants::ContentType::PROPOSAL
         | 
| 957 | 
            -
                  value | 
| 958 | 
            +
                  value = Melos::Struct::Proposal.new(stream)
         | 
| 958 959 | 
             
                  context << [:proposal, value]
         | 
| 959 960 | 
             
                when Melos::Constants::ContentType::COMMIT
         | 
| 960 | 
            -
                  value | 
| 961 | 
            +
                  value = Melos::Struct::Commit.new(stream)
         | 
| 961 962 | 
             
                  context << [:commit, value]
         | 
| 962 963 | 
             
                end
         | 
| 963 | 
            -
                fcad | 
| 964 | 
            +
                fcad = Melos::Struct::FramedContentAuthData.new_with_content_type(stream, content_type)
         | 
| 964 965 | 
             
                context << [:auth, fcad]
         | 
| 965 966 | 
             
                # assume rest is padding
         | 
| 966 | 
            -
                context << [:padding,  | 
| 967 | 
            +
                context << [:padding, stream.read()]
         | 
| 967 968 | 
             
                instance.send(:set_instance_vars, context)
         | 
| 968 | 
            -
                 | 
| 969 | 
            +
                instance
         | 
| 969 970 | 
             
              end
         | 
| 970 971 |  | 
| 971 972 | 
             
              def content
         | 
    
        data/lib/melos/vec.rb
    CHANGED
    
    | @@ -40,13 +40,6 @@ module Melos::Vec | |
| 40 40 | 
             
                write_varint(str.bytesize) + str
         | 
| 41 41 | 
             
              end
         | 
| 42 42 |  | 
| 43 | 
            -
              def get_prefix_and_length(str)
         | 
| 44 | 
            -
                prefix = str[0].ord >> 6
         | 
| 45 | 
            -
                length = read_varint(str)
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                [prefix, length]
         | 
| 48 | 
            -
              end
         | 
| 49 | 
            -
             | 
| 50 43 | 
             
              def parse_vec(vec_as_string)
         | 
| 51 44 | 
             
                prefix = vec_as_string[0].ord >> 6
         | 
| 52 45 | 
             
                length = read_varint(vec_as_string)
         | 
| @@ -67,23 +60,33 @@ module Melos::Vec | |
| 67 60 | 
             
                [str, rest]
         | 
| 68 61 | 
             
              end
         | 
| 69 62 |  | 
| 70 | 
            -
              def  | 
| 71 | 
            -
                prefix =  | 
| 72 | 
            -
                length =  | 
| 63 | 
            +
              def parse_stringio(vec_as_stringio)
         | 
| 64 | 
            +
                prefix = prefix_from_stringio(vec_as_stringio)
         | 
| 65 | 
            +
                length = length_from_stringio(vec_as_stringio)
         | 
| 73 66 | 
             
                case prefix
         | 
| 74 | 
            -
                when 0
         | 
| 75 | 
            -
                   | 
| 76 | 
            -
                   | 
| 77 | 
            -
                when 1
         | 
| 78 | 
            -
                  first_vec = vec_as_string[0, 2 + length]
         | 
| 79 | 
            -
                  rest = vec_as_string[(2 + length)..]
         | 
| 80 | 
            -
                when 2
         | 
| 81 | 
            -
                  first_vec = vec_as_string[0, 4 + length]
         | 
| 82 | 
            -
                  rest = vec_as_string[(4 + length)..]
         | 
| 67 | 
            +
                when 0..2
         | 
| 68 | 
            +
                  vec_as_stringio.pos = (vec_as_stringio.pos + (2 ** prefix))
         | 
| 69 | 
            +
                  str = vec_as_stringio.read(length)
         | 
| 83 70 | 
             
                else
         | 
| 84 71 | 
             
                  raise ArgumentError.new('invalid header')
         | 
| 85 72 | 
             
                end
         | 
| 86 73 |  | 
| 87 | 
            -
                 | 
| 74 | 
            +
                str
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
              private
         | 
| 78 | 
            +
             | 
| 79 | 
            +
              def prefix_from_stringio(stringio)
         | 
| 80 | 
            +
                pos = stringio.pos
         | 
| 81 | 
            +
                prefix_ = stringio.getbyte >> 6
         | 
| 82 | 
            +
                stringio.seek(pos)
         | 
| 83 | 
            +
                prefix_
         | 
| 84 | 
            +
              end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
              def length_from_stringio(stringio)
         | 
| 87 | 
            +
                pos = stringio.pos
         | 
| 88 | 
            +
                buf = stringio.read(4)
         | 
| 89 | 
            +
                stringio.seek(pos)
         | 
| 90 | 
            +
                read_varint(buf)
         | 
| 88 91 | 
             
              end
         | 
| 89 92 | 
             
            end
         | 
    
        data/lib/melos/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,13 +1,13 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: melos
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.0. | 
| 4 | 
            +
              version: 0.0.2
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Ryo Kajiwara
         | 
| 8 8 | 
             
            bindir: exe
         | 
| 9 9 | 
             
            cert_chain: []
         | 
| 10 | 
            -
            date:  | 
| 10 | 
            +
            date: 1980-01-02 00:00:00.000000000 Z
         | 
| 11 11 | 
             
            dependencies:
         | 
| 12 12 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 13 13 | 
             
              name: hpke
         | 
| @@ -50,7 +50,6 @@ executables: [] | |
| 50 50 | 
             
            extensions: []
         | 
| 51 51 | 
             
            extra_rdoc_files: []
         | 
| 52 52 | 
             
            files:
         | 
| 53 | 
            -
            - ".rspec"
         | 
| 54 53 | 
             
            - LICENSE.txt
         | 
| 55 54 | 
             
            - README.md
         | 
| 56 55 | 
             
            - Rakefile
         | 
| @@ -104,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 104 103 | 
             
                - !ruby/object:Gem::Version
         | 
| 105 104 | 
             
                  version: '0'
         | 
| 106 105 | 
             
            requirements: []
         | 
| 107 | 
            -
            rubygems_version: 3. | 
| 106 | 
            +
            rubygems_version: 3.7.1
         | 
| 108 107 | 
             
            specification_version: 4
         | 
| 109 108 | 
             
            summary: Messaging Layer Security Protocol (RFC 9420) on Ruby
         | 
| 110 109 | 
             
            test_files: []
         | 
    
        data/.rspec
    DELETED