baseh 1.1.0 → 2.0.2
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/README.md +117 -27
- data/lib/baseh/baseh.rb +302 -25
- data/lib/baseh/basen.rb +7 -0
- data/lib/baseh/checksum.rb +13 -6
- data/lib/baseh/feistel.rb +27 -14
- data/lib/baseh/profile.rb +143 -12
- data/lib/baseh/profiles.rb +97 -30
- data/lib/baseh/version.rb +1 -1
- data/lib/baseh.rb +89 -11
- metadata +6 -4
- data/lib/baseh/zero.rb +0 -71
data/lib/baseh.rb
CHANGED
|
@@ -10,54 +10,132 @@ require_relative "baseh/checksum"
|
|
|
10
10
|
require_relative "baseh/feistel"
|
|
11
11
|
require_relative "baseh/profiles"
|
|
12
12
|
require_relative "baseh/baseh"
|
|
13
|
-
require_relative "baseh/zero"
|
|
14
13
|
|
|
15
14
|
# baseH (Human Reference Code) codec. See spec/IMPLEMENTATION_CODEC.md in the
|
|
16
15
|
# repository root for the normative specification.
|
|
17
16
|
module Baseh
|
|
17
|
+
# The frozen published permutation key used by every plain tier helper.
|
|
18
|
+
# Public by design: it makes issued codes look non-sequential but offers no
|
|
19
|
+
# secrecy, since anyone can read it here. Never swap it on a live
|
|
20
|
+
# namespace; codes only decode with the key they were issued under. Use
|
|
21
|
+
# the -p helpers to supply private key material.
|
|
22
|
+
FROZEN_KEY_BYTES = Profiles::FROZEN_KEY_BYTES
|
|
23
|
+
|
|
18
24
|
class << self
|
|
19
25
|
# Frozen tier baseh-minimum-v1: alphanumeric with no strips, no checksum,
|
|
20
|
-
# hyphen-delimited XXX-XXX.
|
|
26
|
+
# hyphen-delimited XXX-XXX. Permutes with the frozen published key.
|
|
21
27
|
def baseh_minimum_v1
|
|
22
28
|
Profiles.baseh_minimum_v1
|
|
23
29
|
end
|
|
24
30
|
|
|
25
|
-
# baseh-minimum with
|
|
31
|
+
# baseh-minimum permuted with caller-supplied key material. key_bytes is required.
|
|
26
32
|
def baseh_minimum_p_v1(key_bytes:, key_id: "default", rounds: 8)
|
|
27
33
|
Profiles.baseh_minimum_p_v1(key_bytes: key_bytes, key_id: key_id, rounds: rounds)
|
|
28
34
|
end
|
|
29
35
|
|
|
30
|
-
# Frozen tier baseh-light-v1: visual light plus spoken light,
|
|
31
|
-
# checksum
|
|
36
|
+
# Frozen tier baseh-light-v1: visual light plus spoken light, two
|
|
37
|
+
# checksum symbols, hyphen-delimited. Permutes with the frozen published
|
|
38
|
+
# key.
|
|
32
39
|
def baseh_light_v1
|
|
33
40
|
Profiles.baseh_light_v1
|
|
34
41
|
end
|
|
35
42
|
|
|
36
|
-
# baseh-light with
|
|
43
|
+
# baseh-light permuted with caller-supplied key material. key_bytes is required.
|
|
37
44
|
def baseh_light_p_v1(key_bytes:, key_id: "default", rounds: 8)
|
|
38
45
|
Profiles.baseh_light_p_v1(key_bytes: key_bytes, key_id: key_id, rounds: rounds)
|
|
39
46
|
end
|
|
40
47
|
|
|
41
|
-
# Frozen tier baseh-medium-v1: visual medium plus spoken medium,
|
|
42
|
-
# checksum
|
|
48
|
+
# Frozen tier baseh-medium-v1: visual medium plus spoken medium, two
|
|
49
|
+
# checksum symbols, hyphen-delimited. The default. Permutes with the
|
|
50
|
+
# frozen published key.
|
|
43
51
|
def baseh_medium_v1
|
|
44
52
|
Profiles.baseh_medium_v1
|
|
45
53
|
end
|
|
46
54
|
|
|
47
|
-
# baseh-medium with
|
|
55
|
+
# baseh-medium permuted with caller-supplied key material. key_bytes is required.
|
|
48
56
|
def baseh_medium_p_v1(key_bytes:, key_id: "default", rounds: 8)
|
|
49
57
|
Profiles.baseh_medium_p_v1(key_bytes: key_bytes, key_id: key_id, rounds: rounds)
|
|
50
58
|
end
|
|
51
59
|
|
|
52
60
|
# Frozen tier baseh-heavy-v1: conservative alphabet plus spoken heavy,
|
|
53
|
-
#
|
|
61
|
+
# two checksum symbols, hyphen-delimited. Permutes with the frozen
|
|
62
|
+
# published key.
|
|
54
63
|
def baseh_heavy_v1
|
|
55
64
|
Profiles.baseh_heavy_v1
|
|
56
65
|
end
|
|
57
66
|
|
|
58
|
-
# baseh-heavy with
|
|
67
|
+
# baseh-heavy permuted with caller-supplied key material. key_bytes is required.
|
|
59
68
|
def baseh_heavy_p_v1(key_bytes:, key_id: "default", rounds: 8)
|
|
60
69
|
Profiles.baseh_heavy_p_v1(key_bytes: key_bytes, key_id: key_id, rounds: rounds)
|
|
61
70
|
end
|
|
71
|
+
|
|
72
|
+
# Frozen tier baseh-expandable-v1: variable-length codes, four characters
|
|
73
|
+
# while the namespace is small and growing one symbol per generation.
|
|
74
|
+
# Permutes per generation with the frozen published key.
|
|
75
|
+
def baseh_expandable_v1
|
|
76
|
+
Profiles.baseh_expandable_v1
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# baseh-expandable permuted with caller-supplied key material. key_bytes is required.
|
|
80
|
+
def baseh_expandable_p_v1(key_bytes:, key_id: "default", rounds: 8)
|
|
81
|
+
Profiles.baseh_expandable_p_v1(key_bytes: key_bytes, key_id: key_id, rounds: rounds)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Zero-config facade over the frozen baseh-expandable-v1 profile, the
|
|
85
|
+
# recommended default for new namespaces. Most applications never need a
|
|
86
|
+
# profile object at all: these methods share one lazily constructed,
|
|
87
|
+
# stateless codec instance (thread-safe once built, per Baseh::Baseh).
|
|
88
|
+
#
|
|
89
|
+
# Baseh.encode(123456) -> String canonical code
|
|
90
|
+
# Baseh.decode(code).id -> 123456
|
|
91
|
+
# Baseh.validate(code).valid -> true/false
|
|
92
|
+
#
|
|
93
|
+
# decode returns the full DecodeResult (id, canonical_code, corrected)
|
|
94
|
+
# and validate returns the full ValidateResult (valid, canonical_code,
|
|
95
|
+
# reason), exactly as the instance API does.
|
|
96
|
+
|
|
97
|
+
# Encode an identifier with the default expandable profile.
|
|
98
|
+
#
|
|
99
|
+
# @param id [Integer] any non-negative id
|
|
100
|
+
# @return [String] canonical code
|
|
101
|
+
# @raise [BasehError] OUT_OF_RANGE, BLOCKED_CODE
|
|
102
|
+
def encode(id)
|
|
103
|
+
default.encode(id: id)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Decode a code from the default expandable profile.
|
|
107
|
+
#
|
|
108
|
+
# @param input [String]
|
|
109
|
+
# @param options keyword options of Baseh::Baseh#decode (accept_spaces:,
|
|
110
|
+
# try_correction:, confusion_profile:, max_corrections:)
|
|
111
|
+
# @return [Baseh::Baseh::DecodeResult]
|
|
112
|
+
# @raise [BasehError] same codes as the instance decode
|
|
113
|
+
def decode(input, **options)
|
|
114
|
+
default.decode(input, **options)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Live as-you-type inspection of input against the default expandable
|
|
118
|
+
# profile (spec 12.5). Never raises on user input.
|
|
119
|
+
#
|
|
120
|
+
# @param input [String]
|
|
121
|
+
# @return [Baseh::Baseh::InspectResult]
|
|
122
|
+
def inspect(input)
|
|
123
|
+
default.inspect(input)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Validate a code against the default expandable profile without raising.
|
|
127
|
+
#
|
|
128
|
+
# @param input [String]
|
|
129
|
+
# @param options keyword options of Baseh::Baseh#validate (the same
|
|
130
|
+
# keyword options decode accepts)
|
|
131
|
+
# @return [Baseh::Baseh::ValidateResult]
|
|
132
|
+
def validate(input, **options)
|
|
133
|
+
default.validate(input, **options)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# The shared default-profile codec, built on first use.
|
|
137
|
+
def default
|
|
138
|
+
@default ||= Baseh.new(Profiles.baseh_expandable_v1)
|
|
139
|
+
end
|
|
62
140
|
end
|
|
63
141
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: baseh
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- cloudyventures
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -28,6 +28,7 @@ description: 'Encodes and decodes human reference codes per the baseH codec spec
|
|
|
28
28
|
fixed-length base-N bodies, rolling polynomial checksums, optional feistel-v1 permutation,
|
|
29
29
|
spoken-confusion correction and profanity safety.'
|
|
30
30
|
email:
|
|
31
|
+
- mat@cloudyventures.com
|
|
31
32
|
executables: []
|
|
32
33
|
extensions: []
|
|
33
34
|
extra_rdoc_files: []
|
|
@@ -43,12 +44,13 @@ files:
|
|
|
43
44
|
- lib/baseh/profile.rb
|
|
44
45
|
- lib/baseh/profiles.rb
|
|
45
46
|
- lib/baseh/version.rb
|
|
46
|
-
|
|
47
|
-
homepage:
|
|
47
|
+
homepage: https://github.com/cloudyventures/baseh
|
|
48
48
|
licenses:
|
|
49
49
|
- AGPL-3.0
|
|
50
50
|
metadata:
|
|
51
51
|
rubygems_mfa_required: 'true'
|
|
52
|
+
source_code_uri: https://github.com/cloudyventures/baseh
|
|
53
|
+
bug_tracker_uri: https://github.com/cloudyventures/baseh/issues
|
|
52
54
|
post_install_message:
|
|
53
55
|
rdoc_options: []
|
|
54
56
|
require_paths:
|
data/lib/baseh/zero.rb
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Baseh
|
|
4
|
-
# Zero-config pair over the frozen baseh-medium-v1 profile. No profile
|
|
5
|
-
# object, no key: just the two functions an application needs when it
|
|
6
|
-
# does not want to think about configuration.
|
|
7
|
-
#
|
|
8
|
-
# Baseh.to_code(481890303) -> "ZZZZZZV"
|
|
9
|
-
# Baseh.from_code("ZZZZZZV") -> 481890303
|
|
10
|
-
#
|
|
11
|
-
# to_code accepts an Integer or a decimal string of digits. from_code
|
|
12
|
-
# strips every whitespace character (edges and internal), accepts
|
|
13
|
-
# lowercase and the typed aliases (O, I, L) and returns the id as an
|
|
14
|
-
# Integer. Any invalid input raises BasehError, including the rare
|
|
15
|
-
# BLOCKED_CODE identifiers that spell a blocklisted word; no correction
|
|
16
|
-
# attempts are ever made.
|
|
17
|
-
module Zero
|
|
18
|
-
DECIMAL = /\A[0-9]+\z/.freeze
|
|
19
|
-
WHITESPACE = /\s+/.freeze
|
|
20
|
-
|
|
21
|
-
ZERO = Baseh.new(Profiles.baseh_medium_v1)
|
|
22
|
-
|
|
23
|
-
module_function
|
|
24
|
-
|
|
25
|
-
# Encode an identifier with the zero-config Medium profile.
|
|
26
|
-
#
|
|
27
|
-
# @param id [Integer, String] Integer or decimal string of digits
|
|
28
|
-
# @return [String] canonical code
|
|
29
|
-
# @raise [ArgumentError] when id is neither an Integer nor a decimal string
|
|
30
|
-
# @raise [BasehError] OUT_OF_RANGE, BLOCKED_CODE
|
|
31
|
-
def to_code(id)
|
|
32
|
-
value =
|
|
33
|
-
case id
|
|
34
|
-
when Integer then id
|
|
35
|
-
when String
|
|
36
|
-
if DECIMAL.match?(id)
|
|
37
|
-
id.to_i
|
|
38
|
-
else
|
|
39
|
-
raise ArgumentError,
|
|
40
|
-
"to_code expects a non-negative Integer or a decimal string"
|
|
41
|
-
end
|
|
42
|
-
else
|
|
43
|
-
raise ArgumentError,
|
|
44
|
-
"to_code expects a non-negative Integer or a decimal string"
|
|
45
|
-
end
|
|
46
|
-
ZERO.encode(id: value)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Decode a code from the zero-config Medium profile back to its id.
|
|
50
|
-
#
|
|
51
|
-
# @param code [String]
|
|
52
|
-
# @return [Integer]
|
|
53
|
-
# @raise [BasehError] INVALID_LENGTH, INVALID_CHARACTER, INVALID_CHECKSUM
|
|
54
|
-
def from_code(code)
|
|
55
|
-
input = code.is_a?(String) ? code.gsub(WHITESPACE, "") : code
|
|
56
|
-
ZERO.decode(input).id
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
class << self
|
|
61
|
-
# See Baseh::Zero.to_code.
|
|
62
|
-
def to_code(id)
|
|
63
|
-
Zero.to_code(id)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# See Baseh::Zero.from_code.
|
|
67
|
-
def from_code(code)
|
|
68
|
-
Zero.from_code(code)
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|