branca-ruby 1.0.4 → 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: a18a64bfdd3003e06e8cf821c88851354edab732357676b3b86014a62fd6a172
4
- data.tar.gz: 281778e2b9e47f9ba6d92a982c5b7e3adfae2cab7a0eee7e2e1610ea143677eb
3
+ metadata.gz: ca468058d67919573d667e3031f3d330ab5407afd95e7c01f4fa0a1f14221420
4
+ data.tar.gz: 4a55b87679fbd08a1cc364b245af0aecc7c5e1db1b87c2e7594e14ce15feef69
5
5
  SHA512:
6
- metadata.gz: 8b80e5b4e5df1e1ffa41ac6cf95c2dd861c9db446cfbed290d84cdafebeea41370ccd819add02d4a5949a3de25c72c5e46179ac700ffefa866f50e8b7b8162eb
7
- data.tar.gz: a37487ab60fe01d67e1ee7d49c4e57e64bb0afda1554a4e5708fcde5c09d450d15497022a9e0190dbab4e200e75037a7c193cd9e192c550e626f7f7a0ae73055
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/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/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.3'
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.4'
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
- 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,28 +58,14 @@ module Branca
58
58
  end
59
59
 
60
60
  def token_explode(token)
61
- raw = base62_decode(token)
62
- bytes = raw.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*')
63
64
  header = bytes.shift(1 + 4 + 24)
64
65
 
65
66
  [header, bytes]
66
67
  end
67
68
 
68
- BASE62_NO_LEADING_ZERO = BaseX.new(
69
- '123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0'
70
- ).freeze
71
-
72
- def base62_encode(raw_token)
73
- BASE62_NO_LEADING_ZERO.encode(raw_token)
74
- end
75
-
76
- def base62_decode(token)
77
- raw = BASE62_NO_LEADING_ZERO.decode(token)
78
- return raw if raw.getbyte(0) == VERSION
79
-
80
- BaseX::Base62.decode(token)
81
- end
82
-
83
69
  def header_explode(header)
84
70
  version = header[0]
85
71
  nonce = header[5..header.size].pack('C*')
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: branca-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thadeu Esteves
@@ -9,20 +9,6 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 2026-02-10 00:00:00.000000000 Z
11
11
  dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: base_x
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - "~>"
17
- - !ruby/object:Gem::Version
18
- version: 0.8.1
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - "~>"
24
- - !ruby/object:Gem::Version
25
- version: 0.8.1
26
12
  - !ruby/object:Gem::Dependency
27
13
  name: rbnacl
28
14
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +94,7 @@ files:
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