Module: Solana::Ruby::Kit::Addresses
- Extended by:
- T::Sig
- Defined in:
- lib/solana/ruby/kit/addresses/curve.rb,
lib/solana/ruby/kit/addresses/address.rb,
lib/solana/ruby/kit/addresses/public_key.rb,
lib/solana/ruby/kit/addresses/program_derived_address.rb
Defined Under Namespace
Classes: Address, OffCurveAddress, ProgramDerivedAddress
Constant Summary
collapse
- CURVE_P =
Field prime p = 2^255 − 19
T.let(T.unsafe(2**255 - 19), Integer)
- CURVE_D =
Curve constant d = −121665/121666 mod p (computed to avoid oversized literals)
T.let((-121665 * 121666.pow(CURVE_P - 2, CURVE_P)) % CURVE_P, Integer)
- CURVE_SQRT_M1 =
sqrt(−1) mod p = 2^((p−1)/4) mod p
T.let(2.pow((CURVE_P - 1) / 4, CURVE_P), Integer)
- ADDRESS_BYTE_LENGTH =
Expected byte length of a Solana address.
T.let(32, Integer)
- ADDRESS_MIN_STR_LEN =
Minimum / maximum character lengths for a base58-encoded 32-byte address.
T.let(32, Integer)
- ADDRESS_MAX_STR_LEN =
T.let(44, Integer)
- ProgramDerivedAddressBump =
The integer bump seed used when deriving a PDA. Must be in [0, 255]. Mirrors TypeScript: ‘type ProgramDerivedAddressBump = Brand<number, ’ProgramDerivedAddressBump’>‘
T.type_alias { Integer }
- Seed =
Accepted seed types mirror TypeScript’s Seeds union:
type Seed = ReadonlyUint8Array | string
In Ruby, a seed is either a binary String or an Integer Array.
T.type_alias { T.any(String, T::Array[Integer]) }
- MAX_SEED_LENGTH =
Maximum byte length of a single seed.
T.let(32, Integer)
- MAX_SEEDS =
Maximum number of seeds per PDA derivation.
T.let(16, Integer)
- PDA_MARKER_BYTES =
Marker bytes appended during hashing: UTF-8 “ProgramDerivedAddress”.
T.let('ProgramDerivedAddress'.b, String)
Class Method Summary
collapse
Class Method Details
.address(putative) ⇒ Object
121
122
123
124
|
# File 'lib/solana/ruby/kit/addresses/address.rb', line 121
def address(putative)
assert_address!(putative)
Address.new(putative)
end
|
.address?(putative) ⇒ Boolean
94
95
96
97
98
99
100
101
102
|
# File 'lib/solana/ruby/kit/addresses/address.rb', line 94
def address?(putative)
return false unless putative.length.between?(ADDRESS_MIN_STR_LEN, ADDRESS_MAX_STR_LEN)
return false unless putative.chars.all? { |c| Encoding::Base58::ALPHABET.include?(c) }
bytes = Encoding::Base58.decode(putative)
bytes.bytesize == ADDRESS_BYTE_LENGTH
rescue ArgumentError
false
end
|
.address_comparator ⇒ Object
129
130
131
|
# File 'lib/solana/ruby/kit/addresses/address.rb', line 129
def address_comparator
->(a, b) { a.value <=> b.value }
end
|
.assert_address!(putative) ⇒ Object
107
108
109
110
111
112
113
114
115
116
|
# File 'lib/solana/ruby/kit/addresses/address.rb', line 107
def assert_address!(putative)
unless putative.length.between?(ADDRESS_MIN_STR_LEN, ADDRESS_MAX_STR_LEN)
Kernel.raise SolanaError.new(
SolanaError::ADDRESSES__STRING_LENGTH_OUT_OF_RANGE,
actual_length: putative.length
)
end
Kernel.raise SolanaError.new(SolanaError::ADDRESSES__INVALID_BASE58_ENCODED_ADDRESS) unless address?(putative)
end
|
.assert_off_curve_address!(addr) ⇒ Object
100
101
102
|
# File 'lib/solana/ruby/kit/addresses/curve.rb', line 100
def assert_off_curve_address!(addr)
Kernel.raise SolanaError.new(SolanaError::ADDRESSES__SEEDS_POINT_ON_CURVE) if on_ed25519_curve?(decode_address(addr))
end
|
.assert_program_derived_address!(value) ⇒ Object
57
58
59
60
|
# File 'lib/solana/ruby/kit/addresses/program_derived_address.rb', line 57
def assert_program_derived_address!(value)
Kernel.raise SolanaError.new(SolanaError::ADDRESSES__INVALID_SEEDS_POINT_ON_CURVE) unless value.is_a?(ProgramDerivedAddress)
Kernel.raise SolanaError.new(SolanaError::ADDRESSES__PDA_BUMP_SEED_OUT_OF_RANGE) unless value.bump.between?(0, 255)
end
|
.create_address_with_seed(base_address:, program_address:, seed:) ⇒ Object
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
# File 'lib/solana/ruby/kit/addresses/program_derived_address.rb', line 120
def create_address_with_seed(base_address:, program_address:, seed:)
seed_bytes = seed.b
if seed_bytes.bytesize > MAX_SEED_LENGTH
Kernel.raise SolanaError.new(
SolanaError::ADDRESSES__MAX_SEED_LENGTH_EXCEEDED,
actual_length: seed_bytes.bytesize
)
end
base_bytes = decode_address(base_address)
program_bytes = decode_address(program_address)
hash_input = base_bytes + seed_bytes + program_bytes
result_bytes = Digest::SHA256.digest(hash_input)
Address.new(encode_address(result_bytes))
end
|
.decode_address(addr) ⇒ Object
81
82
83
84
85
86
87
88
89
|
# File 'lib/solana/ruby/kit/addresses/address.rb', line 81
def decode_address(addr)
bytes = Encoding::Base58.decode(addr.value)
Kernel.raise SolanaError.new(
SolanaError::ADDRESSES__INVALID_BYTE_LENGTH_FOR_ADDRESS,
byte_length: bytes.bytesize
) unless bytes.bytesize == ADDRESS_BYTE_LENGTH
bytes
end
|
.encode_address(bytes) ⇒ Object
69
70
71
72
73
74
75
76
|
# File 'lib/solana/ruby/kit/addresses/address.rb', line 69
def encode_address(bytes)
Kernel.raise SolanaError.new(
SolanaError::ADDRESSES__INVALID_BYTE_LENGTH_FOR_ADDRESS,
byte_length: bytes.bytesize
) unless bytes.bytesize == ADDRESS_BYTE_LENGTH
Encoding::Base58.encode(bytes)
end
|
.get_address_from_public_key(verify_key) ⇒ Object
19
20
21
22
23
24
25
|
# File 'lib/solana/ruby/kit/addresses/public_key.rb', line 19
def get_address_from_public_key(verify_key)
unless verify_key.is_a?(RbNaCl::VerifyKey)
Kernel.raise SolanaError.new(SolanaError::ADDRESSES__INVALID_ED25519_PUBLIC_KEY)
end
Address.new(encode_address(verify_key.to_bytes))
end
|
.get_program_derived_address(program_address:, seeds:) ⇒ Object
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
# File 'lib/solana/ruby/kit/addresses/program_derived_address.rb', line 74
def get_program_derived_address(program_address:, seeds:)
if seeds.length > MAX_SEEDS
Kernel.raise SolanaError.new(SolanaError::ADDRESSES__TOO_MANY_SEEDS)
end
seeds.each do |seed|
seed_bytes = seed_to_bytes(seed)
if seed_bytes.bytesize > MAX_SEED_LENGTH
Kernel.raise SolanaError.new(
SolanaError::ADDRESSES__MAX_SEED_LENGTH_EXCEEDED,
actual_length: seed_bytes.bytesize
)
end
end
program_bytes = decode_address(program_address)
255.downto(0) do |bump|
seed_bytes_list = seeds.map { |s| seed_to_bytes(s) }
bump_bytes = [bump].pack('C').b
hash_input = (seed_bytes_list + [bump_bytes, program_bytes, PDA_MARKER_BYTES]).join
candidate_bytes = Digest::SHA256.digest(hash_input)
next if on_ed25519_curve?(candidate_bytes)
candidate_address = Address.new(encode_address(candidate_bytes))
return ProgramDerivedAddress.new(address: candidate_address, bump: bump)
end
Kernel.raise SolanaError.new(SolanaError::ADDRESSES__FAILED_TO_FIND_VIABLE_PDA_BUMP_SEED)
end
|
.get_public_key_from_address(addr) ⇒ Object
32
33
34
35
36
37
|
# File 'lib/solana/ruby/kit/addresses/public_key.rb', line 32
def get_public_key_from_address(addr)
bytes = decode_address(addr)
RbNaCl::VerifyKey.new(bytes)
rescue RangeError, ScriptError => e
Kernel.raise SolanaError.new(SolanaError::ADDRESSES__INVALID_ED25519_PUBLIC_KEY)
end
|
.off_curve_address(addr) ⇒ Object
107
108
109
110
|
# File 'lib/solana/ruby/kit/addresses/curve.rb', line 107
def off_curve_address(addr)
assert_off_curve_address!(addr)
OffCurveAddress.new(addr.value)
end
|
.off_curve_address?(addr) ⇒ Boolean
92
93
94
95
|
# File 'lib/solana/ruby/kit/addresses/curve.rb', line 92
def off_curve_address?(addr)
bytes = decode_address(addr)
off_curve_bytes?(bytes)
end
|
.off_curve_bytes?(bytes) ⇒ Boolean
85
86
87
|
# File 'lib/solana/ruby/kit/addresses/curve.rb', line 85
def off_curve_bytes?(bytes)
!on_ed25519_curve?(bytes)
end
|
.on_ed25519_curve?(bytes) ⇒ Boolean
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
# File 'lib/solana/ruby/kit/addresses/curve.rb', line 41
def on_ed25519_curve?(bytes)
return false unless bytes.bytesize == 32
p = CURVE_P
d = CURVE_D
y_arr = bytes.bytes.dup
x_sign = (y_arr[31] >> 7) & 1
y_arr[31] &= 0x7f
y = y_arr.each_with_index.sum { |b, i| b << (8 * i) }
return false if y >= p
y2 = y.pow(2, p)
u = (y2 - 1) % p
v = (d * y2 + 1) % p
v3 = v.pow(3, p)
v7 = v.pow(7, p)
exp = (p - 5) / 8
x = u * v3 % p * (u * v7 % p).pow(exp, p) % p
vx2 = v * x.pow(2, p) % p
if vx2 == u % p
x = (p - x) % p if (x & 1) != x_sign
return true
end
if vx2 == (p - u) % p
x = x * CURVE_SQRT_M1 % p
x = (p - x) % p if (x & 1) != x_sign
return true
end
false
end
|
.program_derived_address?(value) ⇒ Boolean
46
47
48
49
50
51
52
|
# File 'lib/solana/ruby/kit/addresses/program_derived_address.rb', line 46
def program_derived_address?(value)
return false unless value.is_a?(ProgramDerivedAddress)
return false unless address?(value.address.value)
bump = value.bump
bump.between?(0, 255)
end
|