veriform 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d487a0eb69dec625c5b1a3504ca9efbcc13f2ec2
4
+ data.tar.gz: 1bb60e85054d6c70f692ccdc33311faac9bf57e7
5
+ SHA512:
6
+ metadata.gz: a36d7843ddb5fbb7dc80f48f396f00f0e11972a130c37e6334f56e2f9bc333854117ae7a7639e6a84a6d988ba663ac77195eb7ecf58d5cb2907ad0e94da66a45
7
+ data.tar.gz: 06f7d19d295d31c700b42a9463cf384015a014145712fec5e1638d9598e32a662b153c6473663497851e2e5d7c7f3dd33fda0a8a86d944211af2f085ea738e17
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /.rakeTasks
11
+
12
+ # rspec failure tracking
13
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --format=documentation
3
+ --order random
4
+ --require spec_helper
@@ -0,0 +1,40 @@
1
+ AllCops:
2
+ DisplayCopNames: true
3
+
4
+ #
5
+ # Style
6
+ #
7
+
8
+ Style/ModuleFunction:
9
+ Enabled: false
10
+
11
+ Style/NumericPredicate:
12
+ Enabled: false
13
+
14
+ Style/StringLiterals:
15
+ EnforcedStyle: double_quotes
16
+
17
+ #
18
+ # Metrics
19
+ #
20
+
21
+ Metrics/AbcSize:
22
+ Max: 30
23
+
24
+ Metrics/BlockLength:
25
+ Enabled: false
26
+
27
+ Metrics/ClassLength:
28
+ Max: 100
29
+
30
+ Metrics/CyclomaticComplexity:
31
+ Max: 25
32
+
33
+ Metrics/LineLength:
34
+ Max: 128
35
+
36
+ Metrics/MethodLength:
37
+ Max: 25
38
+
39
+ Metrics/PerceivedComplexity:
40
+ Max: 25
@@ -0,0 +1 @@
1
+ 2.4.1
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+ ruby RUBY_VERSION
5
+
6
+ gemspec
7
+
8
+ group :development, :test do
9
+ gem "benchmark-ips"
10
+ gem "guard-rspec"
11
+ gem "rake"
12
+ gem "rspec", "~> 3.5"
13
+ gem "rubocop", "0.49.1"
14
+ gem "tjson", "~> 0.5"
15
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # More info at https://github.com/guard/guard#readme
4
+
5
+ guard :rspec, cmd: "GUARD_RSPEC=1 bundle exec rspec --no-profile" do
6
+ require "guard/rspec/dsl"
7
+ dsl = Guard::RSpec::Dsl.new(self)
8
+
9
+ # RSpec files
10
+ rspec = dsl.rspec
11
+ watch(rspec.spec_helper) { rspec.spec_dir }
12
+ watch(rspec.spec_support) { rspec.spec_dir }
13
+ watch(rspec.spec_files)
14
+
15
+ # Ruby files
16
+ ruby = dsl.ruby
17
+ dsl.watch_spec_files_for(ruby.lib_files)
18
+ end
@@ -0,0 +1,76 @@
1
+ # veriform.rb [![Latest Version][gem-shield]][gem-link] [![Build Status][build-image]][build-link] [![MIT licensed][license-image]][license-link]
2
+
3
+ [gem-shield]: https://badge.fury.io/rb/veriform.svg
4
+ [gem-link]: https://rubygems.org/gems/veriform
5
+ [build-image]: https://secure.travis-ci.org/zcred/veriform.svg?branch=master
6
+ [build-link]: http://travis-ci.org/zcred/veriform
7
+ [license-image]: https://img.shields.io/badge/license-MIT-blue.svg
8
+ [license-link]: https://github.com/zcred/veriform/blob/master/LICENSE.txt
9
+
10
+ Ruby implementation of **Veriform**: a cryptographically verifiable data
11
+ serialization format inspired by Protocol Buffers, useful for things like
12
+ credentials, transparency logs, and "blockchain" applications.
13
+
14
+ For more information, see the [toplevel README.md].
15
+
16
+ [toplevel README.md]: https://github.com/zcred/veriform/blob/master/README.md
17
+
18
+ ## Help and Discussion
19
+
20
+ Have questions? Want to suggest a feature or change?
21
+
22
+ * [Gitter]: web-based chat about zcred projects including **Veriform**
23
+ * [Google Group]: join via web or email ([zcred+subscribe@googlegroups.com])
24
+
25
+ [Gitter]: https://gitter.im/zcred/Lobby
26
+ [Google Group]: https://groups.google.com/forum/#!forum/zcred
27
+ [zcred+subscribe@googlegroups.com]: mailto:zcred+subscribe@googlegroups.com
28
+
29
+ ## Requirements
30
+
31
+ This library is tested against the following MRI versions:
32
+
33
+ - 2.2
34
+ - 2.3
35
+ - 2.4
36
+
37
+ Other Ruby versions may work, but are not officially supported.
38
+
39
+ ## Installation
40
+
41
+ Add this line to your application's Gemfile:
42
+
43
+ ```ruby
44
+ gem "veriform"
45
+ ```
46
+
47
+ And then execute:
48
+
49
+ $ bundle
50
+
51
+ Or install it yourself as:
52
+
53
+ $ gem install veriform
54
+
55
+ ## API
56
+
57
+ ### Veriform.parse
58
+
59
+ To parse a **veriform** message, use the `Veriform.parse` method:
60
+
61
+ ```ruby
62
+ >> Veriform.parse("\x15\x07\x02\x03\x55".b)
63
+ => {1=>{24=>42}}
64
+ ```
65
+
66
+ ## Contributing
67
+
68
+ Bug reports and pull requests are welcome on GitHub at https://github.com/zcred/veriform
69
+
70
+ ## Copyright
71
+
72
+ Copyright (c) 2017 [The Zcred Developers][AUTHORS].
73
+ See [LICENSE.txt] for further details.
74
+
75
+ [AUTHORS]: https://github.com/zcred/zcred/blob/master/AUTHORS.md
76
+ [LICENSE.txt]: https://github.com/zcred/veriform/blob/master/LICENSE.txt
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+
5
+ require "rspec/core/rake_task"
6
+ RSpec::Core::RakeTask.new
7
+
8
+ require "rubocop/rake_task"
9
+ RuboCop::RakeTask.new
10
+
11
+ task default: %w[spec rubocop]
12
+
13
+ task :bench do
14
+ require "benchmark/ips"
15
+ require "veriform"
16
+
17
+ Benchmark.ips do |b|
18
+ input = "\xE9\xF4\x81\x80\x80\x80@".dup.force_encoding("BINARY").freeze
19
+
20
+ b.report("vint64 encode") { Veriform::Varint.encode(281_474_976_741_993) }
21
+ b.report("vint64 decode") { Veriform::Varint.decode(input) }
22
+
23
+ b.compare!
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "veriform/version"
4
+ require "veriform/exceptions"
5
+
6
+ require "veriform/decoder"
7
+ require "veriform/object"
8
+ require "veriform/parser"
9
+ require "veriform/varint"
10
+ require "veriform/zhash"
11
+
12
+ # Cryptographically verifiable data serialization format inspired by Protocol Buffers
13
+ module Veriform
14
+ # Parse the given self-describing Veriform message
15
+ #
16
+ # @param message [String] binary encoded Veriform message
17
+ #
18
+ # @return [Veriform::Object] `::Hash`-like object representing message
19
+ def self.parse(message)
20
+ parser = Veriform::Parser.new(Veriform::Decoder.new)
21
+ parser.parse(message)
22
+ parser.finish
23
+ end
24
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Veriform
4
+ # Build Veriform::Objects from Veriform's self-describing messages
5
+ class Decoder
6
+ # Create a new decoder object which will construct a Veriform::Object tree
7
+ def initialize
8
+ @stack = [Veriform::Object.new]
9
+ end
10
+
11
+ # Add a uint64 to the current object
12
+ def uint64(id, value)
13
+ raise TypeError, "expected Integer, got #{value.class}" unless value.is_a?(Integer)
14
+ @stack.last[id] = value
15
+ end
16
+
17
+ # Add binary data to the current object
18
+ def binary(id, value)
19
+ raise TypeError, "expected String, got #{value.class}" unless value.is_a?(String)
20
+ raise EncodingError, "expected BINARY encoding, got #{value.encoding}" unless value.encoding == Encoding::BINARY
21
+ @stack.last[id] = value
22
+ end
23
+
24
+ # Push down the internal stack, constructing a new Veriform::Object
25
+ def begin_nested
26
+ @stack << Veriform::Object.new
27
+ end
28
+
29
+ # Complete the pushdown, adding the newly constructed object to the next one in the stack
30
+ def end_nested(id)
31
+ value = @stack.pop
32
+ raise StateError, "not inside a nested message" if @stack.empty?
33
+ @stack.last[id] = value
34
+ end
35
+
36
+ # Finish decoding, returning the parent Veriform::Object
37
+ def finish
38
+ result = @stack.pop
39
+ raise StateError, "objects remaining in stack" unless @stack.empty?
40
+ result
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Veriform
4
+ # Base class of all Veriform errors
5
+ Error = Class.new(StandardError)
6
+
7
+ # Generic parse error
8
+ ParseError = Class.new(Error)
9
+
10
+ # Data is not in the correct character encoding
11
+ EncodingError = Class.new(ParseError)
12
+
13
+ # Unexpected end of input
14
+ TruncatedMessageError = Class.new(ParseError)
15
+
16
+ # Message is larger than our maximum configured size
17
+ OversizeMessageError = Class.new(ParseError)
18
+
19
+ # Nested message structure is too deep
20
+ DepthError = Class.new(ParseError)
21
+
22
+ # Parser is in the wrong state to perform the given task
23
+ StateError = Class.new(ParseError)
24
+
25
+ # Field repeated in message
26
+ DuplicateFieldError = Class.new(ParseError)
27
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module Veriform
6
+ # Key/value pairs ala JSON objects or Protobuf messages
7
+ class Object
8
+ extend Enumerable
9
+ extend Forwardable
10
+
11
+ # Delegate certain Hash functions to the underlying hash
12
+ def_delegators :@fields, :each, :keys
13
+
14
+ # Create a Veriform::Object from a TJSON::Object
15
+ def self.from_tjson(obj)
16
+ raise TypeError, "expected TJSON::Object, got #{obj.class}" unless obj.is_a?(TJSON::Object)
17
+
18
+ new.tap do |result|
19
+ obj.each do |key, value|
20
+ result[Integer(key, 10)] = value.is_a?(TJSON::Object) ? from_tjson(value) : value
21
+ end
22
+ end
23
+ end
24
+
25
+ # Create a new Veriform::Object
26
+ #
27
+ # @return [Veriform::Object]
28
+ def initialize
29
+ @fields = {}
30
+ end
31
+
32
+ # Retrieve the value associated with a field identifier in a Veriform::Object
33
+ #
34
+ # @param key [Integer] field identifier
35
+ #
36
+ # @return [Object] value associated with this key
37
+ def [](key)
38
+ @fields[key]
39
+ end
40
+
41
+ # Sets the value associated with a field identifier
42
+ #
43
+ # @param key [Integer] field identifier
44
+ # @param value [Object] value associated with the given key
45
+ #
46
+ # @raise [TypeError] non-Integer key given
47
+ # @raise [Veriform::DuplicateFieldError] attempt to set field that's already been set
48
+ #
49
+ # @return [Object] newly set value
50
+ def []=(key, value)
51
+ raise TypeError, "key must be an integer: #{key.inspect}" unless key.is_a?(Integer)
52
+ raise RangeError, "key must be positive: #{key.inspect}" if key < 0
53
+ raise DuplicateFieldError, "duplicate field ID: #{key}" if @fields.key?(key)
54
+
55
+ @fields[key] = value
56
+ end
57
+
58
+ # Return a hash representation of this object (and its children).
59
+ # This is akin to an `#as_json` method as seen in e.g. Rails.
60
+ #
61
+ # @return [Hash] a hash representation of this object
62
+ def to_h
63
+ result = {}
64
+
65
+ @fields.each do |k, v|
66
+ result[k] = v.is_a?(self.class) ? v.to_h : v
67
+ end
68
+
69
+ result
70
+ end
71
+
72
+ # Compare two Veriform::Objects by value for equality
73
+ def eql?(other)
74
+ return false unless other.is_a?(self.class)
75
+ return false unless keys.length == other.keys.length
76
+
77
+ keys.each do |key|
78
+ return false unless self[key].eql?(other[key])
79
+ end
80
+
81
+ true
82
+ end
83
+
84
+ alias == eql?
85
+ end
86
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Veriform
4
+ # Parses encoded Veriform messages, invoking callbacks in the given handler
5
+ # (i.e. this is a "push parser" which supports different backends)
6
+ class Parser
7
+ # Default maximum length of a Veriform message. This is a conservative choice
8
+ # as Veriform's main intended use is a credential format.
9
+ MAX_LENGTH = 1024
10
+
11
+ # Default maximum depth (i.e. number of levels of child objects)
12
+ MAX_DEPTH = 8
13
+
14
+ # Create a new message parser with the given parse event handler
15
+ def initialize(handler, max_length = MAX_LENGTH, max_depth = MAX_DEPTH)
16
+ @handler = handler
17
+ @max_length = max_length
18
+ @max_depth = max_depth
19
+ @remaining = []
20
+ end
21
+
22
+ # Parse the given Veriform message, invoking callbacks as necessary
23
+ def parse(msg)
24
+ raise OversizeMessageError, "length #{msg.length} exceeds max of #{@max_length}" if msg.length > @max_length
25
+ raise EncodingError, "expected BINARY encoding, got #{msg.encoding}" unless msg.encoding == Encoding::BINARY
26
+ @remaining << msg
27
+
28
+ raise DepthError, "exceeded max depth of #{@max_depth}" if @remaining.size > @max_depth
29
+
30
+ until @remaining.last.empty?
31
+ id, wiretype = parse_field_prefix
32
+
33
+ case wiretype
34
+ when 0 then parse_uint64(id)
35
+ when 2 then parse_message(id)
36
+ when 3 then parse_binary(id)
37
+ else raise ParseError, "unknown wiretype: #{wiretype.inspect}"
38
+ end
39
+ end
40
+
41
+ @remaining.pop
42
+
43
+ true
44
+ end
45
+
46
+ # Finish parsing, returning the resulting object produced by the builder
47
+ def finish
48
+ @handler.finish
49
+ end
50
+
51
+ private
52
+
53
+ # Parse a varint which also stores a wiretype
54
+ def parse_field_prefix
55
+ result, remaining = Veriform::Varint.decode(@remaining.pop)
56
+ @remaining << remaining
57
+ wiretype = result & 0x7
58
+ [result >> 3, wiretype]
59
+ end
60
+
61
+ # Parse an unsigned 64-bit integer
62
+ def parse_uint64(id)
63
+ value, remaining = Veriform::Varint.decode(@remaining.pop)
64
+ @remaining << remaining
65
+ @handler.uint64(id, value)
66
+ end
67
+
68
+ # Parse a data type stored with a length prefix
69
+ def parse_length_prefixed_data
70
+ length, remaining = Veriform::Varint.decode(@remaining.pop)
71
+ raise TruncatedMessageError, "not enough bytes remaining in input" if remaining.bytesize < length
72
+ data = remaining.byteslice(0, length)
73
+ @remaining << remaining.byteslice(length, remaining.bytesize - length)
74
+ data
75
+ end
76
+
77
+ # Parse a nested message
78
+ def parse_message(id)
79
+ @handler.begin_nested
80
+ parse(parse_length_prefixed_data)
81
+ @handler.end_nested(id)
82
+ end
83
+
84
+ # Parse length-prefixed binary data
85
+ def parse_binary(id)
86
+ @handler.binary(id, parse_length_prefixed_data)
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,106 @@
1
+ # encoding: binary
2
+ # frozen_string_literal: true
3
+
4
+ module Veriform
5
+ # Little Endian 64-bit Unsigned Prefix Varints
6
+ module Varint
7
+ # Maximum value we can encode as a vint64
8
+ MAX = (2**64) - 1
9
+
10
+ # :nodoc: Lookup table for the number of trailing zeroes in a byte
11
+ CTZ_TABLE = [8, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
12
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
13
+ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
14
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
15
+ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
16
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
17
+ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
18
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
19
+ 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
20
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
21
+ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
22
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
23
+ 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
24
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
25
+ 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,
26
+ 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0].freeze
27
+
28
+ # Encode the given unsignedinteger value as a vint64
29
+ #
30
+ # @param value [Integer] unsigned integer value to encode as a vint64
31
+ #
32
+ # @raise [TypeError] non-integer value given
33
+ # @raise [ArgumentError] value outside the unsigned 64-bit integer range
34
+ #
35
+ # @return [String] serialized vint64 value
36
+ def self.encode(value)
37
+ raise TypeError, "value must be an Integer" unless value.is_a?(Integer)
38
+ raise ArgumentError, "value must be zero or greater" if value < 0
39
+ raise ArgumentError, "value must be in the 64-bit unsigned range" if value > MAX
40
+
41
+ length = 1
42
+ result = (value << 1) | 1
43
+ max = 1 << 7
44
+
45
+ while value >= max
46
+ # 9-byte special case
47
+ return [0, value].pack("CQ<") if length == 8
48
+
49
+ result <<= 1
50
+ max <<= 7
51
+ length += 1
52
+ end
53
+
54
+ [result].pack("Q<")[0, length].force_encoding(Encoding::BINARY)
55
+ end
56
+
57
+ # Decode a vint64-serialized value into an unsignedinteger
58
+ #
59
+ # @param input [String] serialized vint64 to decode
60
+ #
61
+ # @raise [TypeError] non-String input given
62
+ # @raise [ArgumentError] empty input given
63
+ #
64
+ # @return [Array<Integer, String>] decoded integer and remaining data
65
+ def self.decode(input)
66
+ raise TypeError, "input must be a String" unless input.is_a?(String)
67
+ raise ArgumentError, "input cannot be empty" if input.empty?
68
+
69
+ prefix = input.getbyte(0)
70
+ input_len = input.bytesize
71
+
72
+ # 9-byte special case
73
+ if prefix.zero?
74
+ raise TruncatedMessageError, "not enough bytes to decode varint" if input_len < 9
75
+ length = 9
76
+ result = decode_le64(input[1, 8])
77
+ else
78
+ # Count trailing zeroes
79
+ length = CTZ_TABLE[prefix] + 1
80
+ result = decode_le64(input[0, length]) >> length
81
+ raise TruncatedMessageError, "not enough bytes to decode varint" if input_len < length
82
+ end
83
+
84
+ if length > 1 && result < (1 << (7 * (length - 1)))
85
+ raise ParseError, "malformed varint"
86
+ end
87
+
88
+ [result, input.byteslice(length, input_len - length)]
89
+ end
90
+
91
+ class << self
92
+ private
93
+
94
+ # Decode a little endian integer (without allocating memory, unlike pack)
95
+ def decode_le64(bytes)
96
+ result = 0
97
+
98
+ (bytes.bytesize - 1).downto(0) do |i|
99
+ result = (result << 8) | bytes.getbyte(i)
100
+ end
101
+
102
+ result
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Veriform
4
+ VERSION = "0.0.0"
5
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Veriform
6
+ # Computes astructured hash of a Veriform message
7
+ class Zhash
8
+ # One character "tag" values used to separate zhash domains
9
+ module Tags
10
+ # "Objects" represent Veriform messages
11
+ OBJECT = "O"
12
+
13
+ # 8-bit clean binary data
14
+ BINARY = "d"
15
+
16
+ # 64-bit unsigned integers
17
+ UINT64 = "u"
18
+ end
19
+
20
+ # By default we compute zhashes using SHA-256
21
+ DEFAULT_HASH_ALGORITHM = Digest::SHA256
22
+
23
+ # Calculate the zhash digest of the given object
24
+ #
25
+ # @param algorithm [Class] a class which behaves like a `Digest`
26
+ #
27
+ # @return [String] bytestring containing the resulting digest
28
+ def self.digest(object, algorithm: DEFAULT_HASH_ALGORITHM)
29
+ new(algorithm: algorithm).digest(object)
30
+ end
31
+
32
+ # Calculate an object's zhash digest, hex encoding the result.
33
+ # Takes the same parameters as `Veriform::Zhash.digest`
34
+ #
35
+ # @return [String] hex encoded string containing the resulting digest
36
+ def self.hexdigest(object, **args)
37
+ digest(object, **args).unpack("H*").first
38
+ end
39
+
40
+ # Create a new `Zhash` instance
41
+ #
42
+ # @param algorithm [Class] a class which behaves like a `Digest` (i.e. implements `reset`, `update`, `digest`)
43
+ #
44
+ # @return [Veriform::Zhash]
45
+ def initialize(algorithm: DEFAULT_HASH_ALGORITHM)
46
+ @algorithm = algorithm
47
+ @hasher = algorithm.new
48
+ end
49
+
50
+ # Compute the zhash of any object allowed in a Veriform message
51
+ #
52
+ # @param object [Veriform::Object, String, Integer] object to compute a Zhash from
53
+ #
54
+ # @return [String] bytestring containing the resulting digest
55
+ def digest(object)
56
+ case object
57
+ when Veriform::Object, Hash then object_digest(object)
58
+ when String then binary_digest(object)
59
+ when Integer then uint64_digest(object)
60
+ else raise TypeError, "can't compute zhash of #{object.class}"
61
+ end
62
+ end
63
+
64
+ # Calculate an object's zhash digest, hex encoding the result.
65
+ # Takes the same parameters as `Veriform::Zhash#digest`
66
+ #
67
+ # @return [String] hex encoded string containing the resulting digest
68
+ def hexdigest(object)
69
+ digest(object).unpack("H*").first
70
+ end
71
+
72
+ private
73
+
74
+ # Compute digest of a `Veriform::Object`
75
+ def object_digest(message)
76
+ hasher = @algorithm.new
77
+ hasher.update Tags::OBJECT
78
+
79
+ message.keys.sort.each do |key|
80
+ hasher.update(encode_uint64(key))
81
+ hasher.update(digest(message[key]))
82
+ end
83
+
84
+ hasher.digest
85
+ end
86
+
87
+ # Compute digest of a bytestring
88
+ def binary_digest(bytes)
89
+ raise EncodingError, "expected BINARY encoding, got #{value.encoding}" unless bytes.encoding == Encoding::BINARY
90
+ compute_tagged_digest(Tags::BINARY, bytes)
91
+ end
92
+
93
+ # Compute digest of an `Integer`
94
+ def uint64_digest(value)
95
+ compute_tagged_digest(Tags::UINT64, encode_uint64(value))
96
+ end
97
+
98
+ # Compute a hash of the given bytes, tweaking the first byte of the
99
+ # resulting digest with the given domain separator tag.
100
+ def compute_tagged_digest(tag, bytes)
101
+ raise ArgumentError, "tag must be 1-byte" if tag.bytesize != 1
102
+ @hasher.reset
103
+ @hasher.update(tag)
104
+ @hasher.update(bytes)
105
+ @hasher.digest
106
+ end
107
+
108
+ # Encode a uin64 value as little endian
109
+ #
110
+ # @param value [Integer] a positive, up-to-64-bit integer value
111
+ #
112
+ # @return [String] a bytestring containing a little endian-encoded value
113
+ def encode_uint64(value)
114
+ raise TypeError, "expected Integer, got #{value.class}" unless value.is_a?(Integer)
115
+ raise RangeError, "integer value must be positive" if value < 0
116
+ raise RangeError, "integer value exceeds 2**64-1" if value > 18_446_744_073_709_551_615
117
+ [value].pack("Q<")
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path("../lib", __FILE__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+ require "veriform/version"
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = "veriform"
10
+ spec.version = Veriform::VERSION
11
+ spec.authors = ["Tony Arcieri"]
12
+ spec.email = ["bascule@gmail.com"]
13
+ spec.summary = "Cryptographically verifiable data serialization format inspired by Protocol Buffers"
14
+ spec.description = <<-DESCRIPTION.strip.gsub(/\s+/, " ")
15
+ Cryptographically verifiable data serialization format inspired by Protocol Buffers,
16
+ useful for things like credentials, transparency logs, and blockchain applications.
17
+ Misuse-resistant symmetric encryption using the AES-SIV (RFC 5297)
18
+ DESCRIPTION
19
+ spec.homepage = "https://github.com/zcred/veriform/tree/master/ruby/"
20
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.required_ruby_version = ">= 2.2.2"
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.14"
28
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: veriform
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Tony Arcieri
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-11-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ description: Cryptographically verifiable data serialization format inspired by Protocol
28
+ Buffers, useful for things like credentials, transparency logs, and blockchain applications.
29
+ Misuse-resistant symmetric encryption using the AES-SIV (RFC 5297)
30
+ email:
31
+ - bascule@gmail.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - ".gitignore"
37
+ - ".rspec"
38
+ - ".rubocop.yml"
39
+ - ".ruby-version"
40
+ - Gemfile
41
+ - Guardfile
42
+ - README.md
43
+ - Rakefile
44
+ - lib/veriform.rb
45
+ - lib/veriform/decoder.rb
46
+ - lib/veriform/exceptions.rb
47
+ - lib/veriform/object.rb
48
+ - lib/veriform/parser.rb
49
+ - lib/veriform/varint.rb
50
+ - lib/veriform/version.rb
51
+ - lib/veriform/zhash.rb
52
+ - veriform.gemspec
53
+ homepage: https://github.com/zcred/veriform/tree/master/ruby/
54
+ licenses: []
55
+ metadata: {}
56
+ post_install_message:
57
+ rdoc_options: []
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 2.2.2
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubyforge_project:
72
+ rubygems_version: 2.6.12
73
+ signing_key:
74
+ specification_version: 4
75
+ summary: Cryptographically verifiable data serialization format inspired by Protocol
76
+ Buffers
77
+ test_files: []