Class: Solana::Keypair

Inherits:
Object
  • Object
show all
Defined in:
lib/solana/keypair.rb

Constant Summary collapse

BASE58_ALPHABET =
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(signing_key) ⇒ Keypair

Returns a new instance of Keypair.



11
12
13
14
# File 'lib/solana/keypair.rb', line 11

def initialize(signing_key)
  @signing_key = signing_key
  @verify_key = signing_key.verify_key
end

Instance Attribute Details

#signing_keyObject (readonly)

Returns the value of attribute signing_key.



9
10
11
# File 'lib/solana/keypair.rb', line 9

def signing_key
  @signing_key
end

#verify_keyObject (readonly)

Returns the value of attribute verify_key.



9
10
11
# File 'lib/solana/keypair.rb', line 9

def verify_key
  @verify_key
end

Class Method Details

.decode_base58(string) ⇒ Object

Raises:

  • (ArgumentError)


83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/solana/keypair.rb', line 83

def self.decode_base58(string)
  raise ArgumentError, "decode_base58 requires a non-empty String, got #{string.inspect}" if string.nil? || string.empty?

  num = 0
  string.each_char do |c|
    idx = BASE58_ALPHABET.index(c)
    raise ArgumentError, "Invalid base58 character #{c.inspect} (not in alphabet: 0OIl excluded)" unless idx
    num = num * 58 + idx
  end

  hex = num.to_s(16)
  hex = "0" + hex if hex.length.odd?

  # Count leading '1's (zero bytes)
  leading_zeros = string.chars.take_while { |c| c == "1" }.length
  bytes = [("00" * leading_zeros) + hex].pack("H*")
  bytes
end

.encode_base58(bytes) ⇒ Object

— Base58 utilities —



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/solana/keypair.rb', line 64

def self.encode_base58(bytes)
  bytes = bytes.b if bytes.is_a?(String)
  num = bytes.unpack1("H*").to_i(16)

  result = ""
  while num > 0
    num, remainder = num.divmod(58)
    result = BASE58_ALPHABET[remainder] + result
  end

  # Preserve leading zero bytes
  bytes.each_byte do |byte|
    break unless byte == 0
    result = "1" + result
  end

  result
end

.from_base58(secret_key_base58) ⇒ Object

Load from base58-encoded secret key (e.g. from env var)



35
36
37
38
# File 'lib/solana/keypair.rb', line 35

def self.from_base58(secret_key_base58)
  bytes = decode_base58(secret_key_base58)
  from_bytes(bytes)
end

.from_bytes(bytes) ⇒ Object

Load from raw 64-byte secret key (Solana format: 32-byte private + 32-byte public)



22
23
24
25
26
# File 'lib/solana/keypair.rb', line 22

def self.from_bytes(bytes)
  bytes = bytes.pack("C*") if bytes.is_a?(Array)
  private_key = bytes[0, 32]
  new(Ed25519::SigningKey.new(private_key))
end

.from_json_file(path) ⇒ Object

Load from Solana CLI keypair JSON file (array of 64 bytes)



29
30
31
32
# File 'lib/solana/keypair.rb', line 29

def self.from_json_file(path)
  bytes = JSON.parse(File.read(path))
  from_bytes(bytes)
end

.generateObject

Generate a new random keypair



17
18
19
# File 'lib/solana/keypair.rb', line 17

def self.generate
  new(Ed25519::SigningKey.generate)
end

.pubkey_from_base58(address) ⇒ Object



102
103
104
# File 'lib/solana/keypair.rb', line 102

def self.pubkey_from_base58(address)
  decode_base58(address)
end

Instance Method Details

#public_key_bytesObject

Public key as 32 bytes



41
42
43
# File 'lib/solana/keypair.rb', line 41

def public_key_bytes
  @verify_key.to_bytes
end

#sign(message) ⇒ Object

Sign a message



52
53
54
55
# File 'lib/solana/keypair.rb', line 52

def sign(message)
  message = message.pack("C*") if message.is_a?(Array)
  @signing_key.sign(message)
end

#to_base58Object Also known as: address

Public key as base58 string (Solana address)



46
47
48
# File 'lib/solana/keypair.rb', line 46

def to_base58
  self.class.encode_base58(public_key_bytes)
end

#to_bytesObject

Full 64-byte secret key (Solana format)



58
59
60
# File 'lib/solana/keypair.rb', line 58

def to_bytes
  @signing_key.to_bytes + public_key_bytes
end