branca-ruby 1.0.3 → 1.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb38a9b375ff1f3510d4f8be5d535210fa926b1852402900bfaffa78bc86954a
4
- data.tar.gz: eb611a373fb87ac2c44c7ad98ebcfb64cf4a94d53128bb78e86d38c6553bdff8
3
+ metadata.gz: ca468058d67919573d667e3031f3d330ab5407afd95e7c01f4fa0a1f14221420
4
+ data.tar.gz: 4a55b87679fbd08a1cc364b245af0aecc7c5e1db1b87c2e7594e14ce15feef69
5
5
  SHA512:
6
- metadata.gz: 845eb83b9b1fdaa2fea3957cf7b930607af154e3440265fcebade68b2db40dcd0177270d7f5d5016e8d3e4446df2115448b4e8e35964f0facc85e6012cf15334
7
- data.tar.gz: 13f6b3408237cb98b24c4489d37d5b9a9386ed8ddeb5f9cef3473ff951eb9a811b6403c0f6cf005f9d3f8667dc54bb065db95ddaf76ad6f8f04dbe9b062c8098
6
+ metadata.gz: 2b06a8122e6afc30cccbc41fd9292a31f2e27fba1714ed3e5e86b7a1d2581ef435ab33ee37f5e491b514ee1eb7d4d89085509267ddede8def3ff3ea709ebef88
7
+ data.tar.gz: 13ca319d1f6e3d3b2ed11db3c06fd63e78605cea934d332b5500d9ede6d3949f97513be686689d118ec0acaa0b6a8cfa1759b832ab116c937f5039b6e1b639ff
@@ -10,7 +10,7 @@ jobs:
10
10
  runs-on: ubuntu-latest
11
11
  strategy:
12
12
  matrix:
13
- ruby-version: ['2.5', '2.6', '2.7', '3.0']
13
+ ruby-version: ['3.0', '3.1']
14
14
 
15
15
  steps:
16
16
  - uses: actions/checkout@v3
data/.gitpod.yml ADDED
@@ -0,0 +1,8 @@
1
+ # This configuration file was automatically generated by Gitpod.
2
+ # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
3
+ # and commit this file to your remote git repository to share the goodness with others.
4
+
5
+ tasks:
6
+ - init: bin/setup
7
+
8
+
data/CHANGELOG.md ADDED
@@ -0,0 +1,45 @@
1
+ # Changelog
2
+
3
+ ## [1.0.5] - 2025-02-10
4
+
5
+ ### Changed
6
+
7
+ - Replaced the `base_x` gem with a built-in `Branca::Base62` encoder/decoder.
8
+
9
+ The `base_x` gem uses mathematical range-based padding, which prepends a leading `'0'` character
10
+ to the encoded token when `62^len < 256^byte_count`. This is incompatible with the JavaScript
11
+ [base-x](https://github.com/cryptocoinjs/base-x) library used by
12
+ [branca-js](https://github.com/tuupola/branca-js) and all other Branca implementations.
13
+
14
+ The JavaScript `base-x` library follows the Bitcoin-style convention where leading `'0'` characters
15
+ in the encoded string map 1:1 to leading `\x00` bytes in the decoded binary. When it decodes a
16
+ Ruby-generated token that starts with `'0'` (added as mathematical padding), it interprets that
17
+ character as a `\x00` byte, corrupting the version byte from `0xBA` to `0x00` and causing an
18
+ "Invalid token version" error.
19
+
20
+ The new `Branca::Base62` module implements the same Bitcoin-style algorithm, ensuring full
21
+ cross-language compatibility with every Branca implementation listed in the
22
+ [branca-spec](https://github.com/thadeu/branca-spec).
23
+
24
+ ### Removed
25
+
26
+ - Removed the `base_x` gem dependency.
27
+
28
+ ## [1.0.4] - 2026-02-10
29
+
30
+ ### Changed
31
+
32
+ - First attempt to fix the leading `'0'` issue in Base62-encoded tokens by using a rearranged
33
+ alphabet (`BaseX.new('123456789ABCDEF...0')`) to avoid `'0'` as the first character. This
34
+ workaround shifted `'0'` to the end of the numeral set but did not address the root cause:
35
+ the `base_x` gem's mathematical padding algorithm is fundamentally incompatible with the
36
+ JavaScript `base-x` library used by all other Branca implementations.
37
+
38
+ - Added a fallback in `base62_decode` that retries with the original `BaseX::Base62` alphabet
39
+ when the version byte does not match `0xBA`, providing partial backward compatibility.
40
+
41
+ - Added comprehensive tests for Base62 encoding compliance and round-trip validation with
42
+ production-like payloads.
43
+
44
+ > **Note:** This version was superseded by 1.0.5, which fully replaces the `base_x` gem with
45
+ > a Bitcoin-style `Branca::Base62` implementation that is natively compatible with `branca-js`.
data/Gemfile CHANGED
@@ -7,4 +7,4 @@ gemspec
7
7
 
8
8
  ruby '>= 2.5.8'
9
9
 
10
- gem "byebug", "~> 10.0", :group => :test
10
+ gem "byebug", :group => :test
data/README.md CHANGED
@@ -17,7 +17,7 @@ It is possible to use [Branca as an alternative to JWT](https://appelsiini.net/2
17
17
  Add this line to your application's Gemfile, Note that you also must have [libsodium](https://download.libsodium.org/doc/) installed.
18
18
 
19
19
  ```ruby
20
- gem 'branca-ruby', '~> 1.0.2'
20
+ gem 'branca-ruby'
21
21
  ```
22
22
 
23
23
  ## Configure
data/branca-ruby.gemspec CHANGED
@@ -22,7 +22,6 @@ Gem::Specification.new do |spec|
22
22
  spec.required_ruby_version = '>= 2.3.0'
23
23
  spec.require_paths = ['lib']
24
24
 
25
- spec.add_dependency 'base_x', '~> 0.8.1'
26
25
  spec.add_dependency 'rbnacl', '~> 7.0'
27
26
  spec.add_development_dependency 'bundler', '>= 1.14'
28
27
  spec.add_development_dependency 'rake', '~> 10.0'
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Branca
4
+ # Bitcoin-style Base62 encoder/decoder compatible with the JavaScript
5
+ # `base-x` library (https://github.com/cryptocoinjs/base-x).
6
+ #
7
+ # Leading \x00 bytes in the input map 1:1 to leading '0' characters
8
+ # in the encoded output, and vice-versa on decode.
9
+ module Base62
10
+ ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
11
+ BASE = ALPHABET.length
12
+
13
+ ALPHABET_MAP = ALPHABET.each_char.with_index.to_h.freeze
14
+
15
+ class << self
16
+ def encode(data)
17
+ data = data.b
18
+ return '' if data.empty?
19
+
20
+ leading_zeros = count_leading_bytes(data, 0x00)
21
+
22
+ int_val = bytes_to_integer(data, leading_zeros)
23
+
24
+ encoded = integer_to_base62(int_val)
25
+
26
+ (ALPHABET[0] * leading_zeros) + encoded
27
+ end
28
+
29
+ def decode(string)
30
+ return ''.b if string.empty?
31
+
32
+ leading_zeros = count_leading_chars(string, ALPHABET[0])
33
+
34
+ int_val = base62_to_integer(string, leading_zeros)
35
+
36
+ bytes = integer_to_bytes(int_val)
37
+
38
+ ("\x00".b * leading_zeros) + bytes
39
+ end
40
+
41
+ private
42
+
43
+ def count_leading_bytes(data, byte)
44
+ count = 0
45
+ data.each_byte { |b| b == byte ? (count += 1) : break }
46
+ count
47
+ end
48
+
49
+ def count_leading_chars(string, char)
50
+ count = 0
51
+ string.each_char { |c| c == char ? (count += 1) : break }
52
+ count
53
+ end
54
+
55
+ def bytes_to_integer(data, skip)
56
+ int_val = 0
57
+ data.bytes.drop(skip).each { |b| int_val = int_val * 256 + b }
58
+ int_val
59
+ end
60
+
61
+ def integer_to_base62(int_val)
62
+ return '' if int_val.zero?
63
+
64
+ result = ''.b
65
+ while int_val.positive?
66
+ int_val, remainder = int_val.divmod(BASE)
67
+ result << ALPHABET[remainder]
68
+ end
69
+ result.reverse
70
+ end
71
+
72
+ def base62_to_integer(string, skip)
73
+ int_val = 0
74
+ string[skip..].each_char do |c|
75
+ digit = ALPHABET_MAP[c]
76
+ raise ArgumentError, "invalid base62 character: #{c.inspect}" unless digit
77
+
78
+ int_val = int_val * BASE + digit
79
+ end
80
+ int_val
81
+ end
82
+
83
+ def integer_to_bytes(int_val)
84
+ return ''.b if int_val.zero?
85
+
86
+ bytes = []
87
+ while int_val.positive?
88
+ bytes.unshift(int_val & 0xFF)
89
+ int_val >>= 8
90
+ end
91
+ bytes.pack('C*')
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Branca
4
- VERSION = '1.0.3'
4
+ VERSION = '1.0.5'
5
5
  end
data/lib/branca.rb CHANGED
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rbnacl'
4
- require 'base_x'
5
4
 
6
5
  require 'branca/version'
7
6
  require 'branca/exceptions'
8
7
  require 'branca/decoder'
8
+ require 'branca/base62'
9
9
 
10
10
  module Branca
11
11
  class << self
@@ -21,7 +21,7 @@ module Branca
21
21
  ciphertext = cipher.encrypt(nonce, message, header)
22
22
  raw_token = header + ciphertext
23
23
 
24
- BaseX::Base62.encode(raw_token)
24
+ Branca::Base62.encode(raw_token)
25
25
  end
26
26
 
27
27
  def decode(token, ttl: self.ttl, secret_key: self.secret_key)
@@ -58,7 +58,9 @@ module Branca
58
58
  end
59
59
 
60
60
  def token_explode(token)
61
- bytes = BaseX::Base62.decode(token).unpack('C C4 C24 C*')
61
+ raise DecodeError if token.nil? || token.empty?
62
+
63
+ bytes = Branca::Base62.decode(token).unpack('C C4 C24 C*')
62
64
  header = bytes.shift(1 + 4 + 24)
63
65
 
64
66
  [header, bytes]
metadata CHANGED
@@ -1,29 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: branca-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thadeu Esteves
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2022-08-12 00:00:00.000000000 Z
10
+ date: 2026-02-10 00:00:00.000000000 Z
12
11
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: base_x
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: 0.8.1
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: 0.8.1
27
12
  - !ruby/object:Gem::Dependency
28
13
  name: rbnacl
29
14
  requirement: !ruby/object:Gem::Requirement
@@ -104,10 +89,12 @@ files:
104
89
  - ".github/FUNDING.yml"
105
90
  - ".github/workflows/ruby.yml"
106
91
  - ".gitignore"
92
+ - ".gitpod.yml"
107
93
  - ".rspec"
108
94
  - ".rubocop.yml"
109
95
  - ".travis-libsodium.sh"
110
96
  - ".travis.yml"
97
+ - CHANGELOG.md
111
98
  - Gemfile
112
99
  - LICENSE
113
100
  - README.md
@@ -116,6 +103,7 @@ files:
116
103
  - bin/setup
117
104
  - branca-ruby.gemspec
118
105
  - lib/branca.rb
106
+ - lib/branca/base62.rb
119
107
  - lib/branca/decoder.rb
120
108
  - lib/branca/exceptions.rb
121
109
  - lib/branca/version.rb
@@ -123,7 +111,6 @@ homepage: https://github.com/thadeu/branca-ruby
123
111
  licenses:
124
112
  - MIT
125
113
  metadata: {}
126
- post_install_message:
127
114
  rdoc_options: []
128
115
  require_paths:
129
116
  - lib
@@ -138,8 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
138
125
  - !ruby/object:Gem::Version
139
126
  version: '0'
140
127
  requirements: []
141
- rubygems_version: 3.3.7
142
- signing_key:
128
+ rubygems_version: 3.6.2
143
129
  specification_version: 4
144
130
  summary: Authenticated and encrypted API tokens using modern crypto
145
131
  test_files: []