veriform 0.0.0

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.
@@ -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: []