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 +4 -4
- data/.github/workflows/ruby.yml +1 -1
- data/CHANGELOG.md +45 -0
- data/README.md +1 -1
- data/branca-ruby.gemspec +0 -1
- data/lib/branca/base62.rb +95 -0
- data/lib/branca/version.rb +1 -1
- data/lib/branca.rb +5 -19
- metadata +3 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ca468058d67919573d667e3031f3d330ab5407afd95e7c01f4fa0a1f14221420
|
|
4
|
+
data.tar.gz: 4a55b87679fbd08a1cc364b245af0aecc7c5e1db1b87c2e7594e14ce15feef69
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2b06a8122e6afc30cccbc41fd9292a31f2e27fba1714ed3e5e86b7a1d2581ef435ab33ee37f5e491b514ee1eb7d4d89085509267ddede8def3ff3ea709ebef88
|
|
7
|
+
data.tar.gz: 13ca319d1f6e3d3b2ed11db3c06fd63e78605cea934d332b5500d9ede6d3949f97513be686689d118ec0acaa0b6a8cfa1759b832ab116c937f5039b6e1b639ff
|
data/.github/workflows/ruby.yml
CHANGED
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'
|
|
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
|
data/lib/branca/version.rb
CHANGED
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
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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
|
+
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
|