xrpl-ruby 0.0.1 → 0.0.3
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/lib/address-codec/address_codec.rb +117 -0
- data/lib/address-codec/codec.rb +98 -0
- data/lib/address-codec/xrp_codec.rb +79 -0
- data/lib/core/base_58_xrp.rb +12 -0
- data/lib/core/base_x.rb +1 -0
- data/lib/core/core.rb +36 -1
- data/lib/xrpl-ruby.rb +5 -1
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 601db38742606f9188b1f301ca9e6800308d7650ec4d6c7dd655db9eed6bab27
|
4
|
+
data.tar.gz: 188e2ff1112b079a7aed430882fe0c0599a839946dd9ed5842d6e55df964c5e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e819838c6ada42db05efebc7fec715ce22dd890911c7a40242a2945bacde3b832f9fbf4139cee2ffbc384a662a0cbda36336cf8e659eebf69820deae25797f0
|
7
|
+
data.tar.gz: 46f08dc1d555d518ce8b98760881e94f29871b1a32c49b6d1003a1197a0e7bd354ec09d415e2ccf4f85f343add4ccb742fdd3587dfabd0f764ed4f397cb47a9a
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module AddressCodec
|
3
|
+
|
4
|
+
class AddressCodec < XrpCodec
|
5
|
+
|
6
|
+
PREFIX_BYTES = {
|
7
|
+
main: [0x05, 0x44],
|
8
|
+
test: [0x04, 0x93]
|
9
|
+
}
|
10
|
+
|
11
|
+
MAX_32_BIT_UNSIGNED_INT = 4294967295
|
12
|
+
|
13
|
+
def classic_address_to_x_address(classic_address, tag, test)
|
14
|
+
account_id = decode_account_id(classic_address)
|
15
|
+
encode_x_address(account_id, tag, test)
|
16
|
+
end
|
17
|
+
|
18
|
+
def encode_x_address(account_id, tag, test)
|
19
|
+
if account_id.length != 20
|
20
|
+
# RIPEMD160 -> 160 Bits = 20 Bytes
|
21
|
+
raise 'Account ID must be 20 bytes'
|
22
|
+
end
|
23
|
+
if tag != false && tag > MAX_32_BIT_UNSIGNED_INT
|
24
|
+
raise 'Invalid tag'
|
25
|
+
end
|
26
|
+
the_tag = tag || 0
|
27
|
+
flag = tag == false || tag.nil? ? 0 : 1
|
28
|
+
|
29
|
+
bytes = concat_args(
|
30
|
+
test ? PREFIX_BYTES[:test] : PREFIX_BYTES[:main],
|
31
|
+
account_id,
|
32
|
+
[
|
33
|
+
flag,
|
34
|
+
the_tag & 0xff,
|
35
|
+
(the_tag >> 8) & 0xff,
|
36
|
+
(the_tag >> 16) & 0xff,
|
37
|
+
(the_tag >> 24) & 0xff,
|
38
|
+
0,
|
39
|
+
0,
|
40
|
+
0,
|
41
|
+
0
|
42
|
+
]
|
43
|
+
)
|
44
|
+
|
45
|
+
encode_checked(bytes)
|
46
|
+
end
|
47
|
+
|
48
|
+
def x_address_to_classic_address(x_address)
|
49
|
+
decoded = decode_x_address(x_address)
|
50
|
+
account_id = decoded[:account_id]
|
51
|
+
tag = decoded[:tag]
|
52
|
+
test = decoded[:test]
|
53
|
+
classic_address = encode_account_id(account_id)
|
54
|
+
{
|
55
|
+
classic_address: classic_address,
|
56
|
+
tag: tag,
|
57
|
+
test: test
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def decode_x_address(x_address)
|
62
|
+
decoded = decode_checked(x_address)
|
63
|
+
test = is_uint8_array_for_test_address(decoded)
|
64
|
+
account_id = decoded[2, 20]
|
65
|
+
tag = tag_from_uint8_array(decoded)
|
66
|
+
{
|
67
|
+
account_id: account_id,
|
68
|
+
tag: tag,
|
69
|
+
test: test
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def valid_x_address?(x_address)
|
74
|
+
begin
|
75
|
+
decode_x_address(x_address)
|
76
|
+
rescue
|
77
|
+
return false
|
78
|
+
end
|
79
|
+
true
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def is_uint8_array_for_test_address(buf)
|
85
|
+
decoded_prefix = buf[0, 2]
|
86
|
+
if decoded_prefix == PREFIX_BYTES[:main]
|
87
|
+
return false
|
88
|
+
end
|
89
|
+
if decoded_prefix == PREFIX_BYTES[:test]
|
90
|
+
return true
|
91
|
+
end
|
92
|
+
|
93
|
+
raise 'Invalid X-address: bad prefix'
|
94
|
+
end
|
95
|
+
|
96
|
+
def tag_from_uint8_array(bytes)
|
97
|
+
flag = bytes[22]
|
98
|
+
if flag >= 2
|
99
|
+
# Keine Unterstützung für 64-Bit-Tags zu diesem Zeitpunkt
|
100
|
+
raise 'Unsupported X-address'
|
101
|
+
end
|
102
|
+
if flag == 1
|
103
|
+
# Little-endian zu Big-endian
|
104
|
+
return bytes[23] + bytes[24] * 0x100 + bytes[25] * 0x10000 + bytes[26] * 0x1000000
|
105
|
+
end
|
106
|
+
if flag != 0
|
107
|
+
raise 'flag must be zero to indicate no tag'
|
108
|
+
end
|
109
|
+
if '0000000000000000' != bytes_to_hex(bytes[23, 8])
|
110
|
+
raise 'remaining bytes must be zero'
|
111
|
+
end
|
112
|
+
false
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'digest'
|
3
|
+
|
4
|
+
require_relative "../core/core"
|
5
|
+
|
6
|
+
module AddressCodec
|
7
|
+
class Codec
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@codec = Core::Base58XRP.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def encode(bytes, opts)
|
14
|
+
versions = opts[:versions]
|
15
|
+
encode_versioned(bytes, versions, opts[:expected_length])
|
16
|
+
end
|
17
|
+
|
18
|
+
def decode(base58string, opts)
|
19
|
+
versions = opts[:versions]
|
20
|
+
types = opts[:version_types]
|
21
|
+
|
22
|
+
without_sum = decode_checked(base58string)
|
23
|
+
|
24
|
+
if versions.length > 1 && !opts[:expected_length]
|
25
|
+
raise 'expected_length is required because there are >= 2 possible versions'
|
26
|
+
end
|
27
|
+
|
28
|
+
version_length_guess = versions[0].is_a?(Numeric) ? 1 : versions[0].length
|
29
|
+
payload_length = opts[:expected_length] || without_sum.length - version_length_guess
|
30
|
+
version_bytes = without_sum[0...-payload_length]
|
31
|
+
payload = without_sum[-payload_length..-1]
|
32
|
+
|
33
|
+
versions.each_with_index do |version, i|
|
34
|
+
version = Array(version)
|
35
|
+
if version_bytes == version
|
36
|
+
return {
|
37
|
+
version: version,
|
38
|
+
bytes: payload,
|
39
|
+
type: types ? types[i] : nil
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
raise 'version_invalid: version bytes do not match any of the provided version(s)'
|
45
|
+
end
|
46
|
+
|
47
|
+
def encode_checked(bytes)
|
48
|
+
check = sha256(sha256(bytes))[0, 4]
|
49
|
+
encode_raw(bytes + check)
|
50
|
+
end
|
51
|
+
|
52
|
+
def decode_checked(base58string)
|
53
|
+
bytes = decode_raw(base58string)
|
54
|
+
|
55
|
+
if bytes.length < 5
|
56
|
+
raise 'invalid_input_size: decoded data must have length >= 5'
|
57
|
+
end
|
58
|
+
|
59
|
+
unless verify_check_sum(bytes)
|
60
|
+
raise 'checksum_invalid'
|
61
|
+
end
|
62
|
+
|
63
|
+
bytes[0...-4]
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def encode_versioned(bytes, versions, expected_length)
|
69
|
+
unless check_byte_length(bytes, expected_length)
|
70
|
+
raise 'unexpected_payload_length: bytes.length does not match expected_length. Ensure that the bytes are a Uint8Array.'
|
71
|
+
end
|
72
|
+
|
73
|
+
encode_checked(concat_args(versions, bytes))
|
74
|
+
end
|
75
|
+
|
76
|
+
def encode_raw(bytes)
|
77
|
+
@codec.encode(bytes.pack('C*'))
|
78
|
+
end
|
79
|
+
|
80
|
+
def decode_raw(base58string)
|
81
|
+
@codec.decode(base58string).unpack('C*')
|
82
|
+
end
|
83
|
+
|
84
|
+
def sha256(bytes)
|
85
|
+
binary_value = bytes.pack('C*')
|
86
|
+
binary_hash = Digest::SHA256.digest(binary_value)
|
87
|
+
binary_hash.unpack('C*')
|
88
|
+
end
|
89
|
+
|
90
|
+
def verify_check_sum(bytes)
|
91
|
+
computed = sha256(sha256(bytes[0...-4]))[0, 4]
|
92
|
+
checksum = bytes[-4, 4]
|
93
|
+
computed == checksum
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../core/core"
|
4
|
+
|
5
|
+
module AddressCodec
|
6
|
+
|
7
|
+
class XrpCodec < Codec
|
8
|
+
|
9
|
+
# base58 encodings: https://xrpl.org/base58-encodings.html
|
10
|
+
ACCOUNT_ID = 0 # Account address (20 bytes)
|
11
|
+
ACCOUNT_PUBLIC_KEY = 0x23 # Account public key (33 bytes)
|
12
|
+
FAMILY_SEED = 0x21 # 33; Seed value (for secret keys) (16 bytes)
|
13
|
+
NODE_PUBLIC = 0x1c # 28; Validation public key (33 bytes)
|
14
|
+
ED25519_SEED = [0x01, 0xe1, 0x4b].freeze # [1, 225, 75]
|
15
|
+
|
16
|
+
def encode_seed(entropy, type = nil)
|
17
|
+
unless check_byte_length(entropy, 16)
|
18
|
+
raise 'entropy must have length 16'
|
19
|
+
end
|
20
|
+
|
21
|
+
opts = {
|
22
|
+
expected_length: 16,
|
23
|
+
versions: type == 'ed25519' ? ED25519_SEED : [FAMILY_SEED]
|
24
|
+
}
|
25
|
+
|
26
|
+
# prefixes entropy with version bytes
|
27
|
+
encode(entropy, opts)
|
28
|
+
end
|
29
|
+
|
30
|
+
def decode_seed(seed, opts = {
|
31
|
+
version_types: ['ed25519', 'secp256k1'],
|
32
|
+
versions: [ED25519_SEED, FAMILY_SEED],
|
33
|
+
expected_length: 16
|
34
|
+
})
|
35
|
+
decode(seed, opts)
|
36
|
+
end
|
37
|
+
|
38
|
+
def encode_account_id(bytes)
|
39
|
+
opts = { versions: [ACCOUNT_ID], expected_length: 20 }
|
40
|
+
encode(bytes, opts)
|
41
|
+
end
|
42
|
+
|
43
|
+
def decode_account_id(account_id)
|
44
|
+
opts = { versions: [ACCOUNT_ID], expected_length: 20 }
|
45
|
+
decode(account_id, opts)[:bytes]
|
46
|
+
end
|
47
|
+
|
48
|
+
def decode_node_public(base58string)
|
49
|
+
opts = { versions: [NODE_PUBLIC], expected_length: 33 }
|
50
|
+
decode(base58string, opts)[:bytes]
|
51
|
+
end
|
52
|
+
|
53
|
+
def encode_node_public(bytes)
|
54
|
+
opts = { versions: [NODE_PUBLIC], expected_length: 33 }
|
55
|
+
encode(bytes, opts)
|
56
|
+
end
|
57
|
+
|
58
|
+
def encode_account_public(bytes)
|
59
|
+
opts = { versions: [ACCOUNT_PUBLIC_KEY], expected_length: 33 }
|
60
|
+
encode(bytes, opts)
|
61
|
+
end
|
62
|
+
|
63
|
+
def decode_account_public(base58string)
|
64
|
+
opts = { versions: [ACCOUNT_PUBLIC_KEY], expected_length: 33 }
|
65
|
+
decode(base58string, opts)[:bytes]
|
66
|
+
end
|
67
|
+
|
68
|
+
def valid_classic_address?(address)
|
69
|
+
begin
|
70
|
+
decode_account_id(address)
|
71
|
+
rescue
|
72
|
+
return false
|
73
|
+
end
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
data/lib/core/base_x.rb
CHANGED
data/lib/core/core.rb
CHANGED
@@ -1,2 +1,37 @@
|
|
1
1
|
# @!attribute
|
2
|
-
require_relative 'base_x'
|
2
|
+
require_relative 'base_x'
|
3
|
+
require_relative 'base_58_xrp'
|
4
|
+
|
5
|
+
def bytes_to_hex(bytes)
|
6
|
+
bytes.pack('C*').unpack1('H*').upcase
|
7
|
+
end
|
8
|
+
|
9
|
+
def hex_to_bytes(hex)
|
10
|
+
[hex].pack('H*').bytes
|
11
|
+
end
|
12
|
+
|
13
|
+
def hex_to_bin(hex)
|
14
|
+
[hex].pack("H*")
|
15
|
+
end
|
16
|
+
|
17
|
+
def bin_to_hex(bin)
|
18
|
+
bin.unpack("H*").first.upcase
|
19
|
+
end
|
20
|
+
|
21
|
+
def check_byte_length(bytes, expected_length)
|
22
|
+
if bytes.respond_to?(:byte_length)
|
23
|
+
bytes.byte_length == expected_length
|
24
|
+
else
|
25
|
+
bytes.length == expected_length
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def concat_args(*args)
|
30
|
+
args.flat_map do |arg|
|
31
|
+
is_scalar?(arg) ? [arg] : arg.to_a
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def is_scalar?(val)
|
36
|
+
val.is_a?(Numeric)
|
37
|
+
end
|
data/lib/xrpl-ruby.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xrpl-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Busse
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-12-
|
11
|
+
date: 2024-12-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -61,6 +61,10 @@ executables: []
|
|
61
61
|
extensions: []
|
62
62
|
extra_rdoc_files: []
|
63
63
|
files:
|
64
|
+
- lib/address-codec/address_codec.rb
|
65
|
+
- lib/address-codec/codec.rb
|
66
|
+
- lib/address-codec/xrp_codec.rb
|
67
|
+
- lib/core/base_58_xrp.rb
|
64
68
|
- lib/core/base_x.rb
|
65
69
|
- lib/core/core.rb
|
66
70
|
- lib/xrpl-ruby.rb
|