Class: Wallet::Wallet

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

Overview

Represents an XRPL wallet, providing methods for signing and address derivation.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(public_key, private_key, seed: nil, classic_address: nil) ⇒ Wallet

Initializes a new Wallet instance.

Parameters:

  • public_key (String)

    The public key as hex.

  • private_key (String)

    The private key as hex.

  • seed (String, nil) (defaults to: nil)

    The encoded seed.

  • classic_address (String, nil) (defaults to: nil)

    The classic address (optional, derived if not provided).



25
26
27
28
29
30
31
32
# File 'lib/wallet/wallet.rb', line 25

def initialize(public_key, private_key, seed: nil, classic_address: nil)
  @public_key = public_key
  @private_key = private_key
  @seed = seed
  @algorithm = public_key.start_with?('ED') ? 'ed25519' : 'secp256k1'
  @key_pairs = KeyPairs::KeyPairs.new
  @classic_address = classic_address || @key_pairs.derive_address(public_key)
end

Instance Attribute Details

#algorithmString (readonly)

Returns The algorithm used ('secp256k1' or 'ed25519').

Returns:

  • (String)

    The algorithm used ('secp256k1' or 'ed25519').



18
19
20
# File 'lib/wallet/wallet.rb', line 18

def algorithm
  @algorithm
end

#classic_addressString (readonly)

Returns The classic address.

Returns:

  • (String)

    The classic address.



16
17
18
# File 'lib/wallet/wallet.rb', line 16

def classic_address
  @classic_address
end

#private_keyString (readonly)

Returns The private key as hex.

Returns:

  • (String)

    The private key as hex.



12
13
14
# File 'lib/wallet/wallet.rb', line 12

def private_key
  @private_key
end

#public_keyString (readonly)

Returns The public key as hex.

Returns:

  • (String)

    The public key as hex.



10
11
12
# File 'lib/wallet/wallet.rb', line 10

def public_key
  @public_key
end

#seedString (readonly)

Returns The encoded seed.

Returns:

  • (String)

    The encoded seed.



14
15
16
# File 'lib/wallet/wallet.rb', line 14

def seed
  @seed
end

Class Method Details

.from_entropy(entropy, algorithm = 'secp256k1') ⇒ Wallet

Creates a wallet from entropy.

Parameters:

  • entropy (Array<Integer>)

    16 bytes of entropy.

  • algorithm (String) (defaults to: 'secp256k1')

    The algorithm to use ('secp256k1' or 'ed25519').

Returns:

  • (Wallet)

    A new Wallet instance.



57
58
59
60
61
# File 'lib/wallet/wallet.rb', line 57

def self.from_entropy(entropy, algorithm = 'secp256k1')
  kp = KeyPairs::KeyPairs.new
  seed = kp.generate_seed(entropy, algorithm)
  from_seed(seed)
end

.from_seed(seed, options = {}) ⇒ Wallet

Creates a wallet from a seed.

Parameters:

  • seed (String)

    The encoded seed.

  • options (Hash) (defaults to: {})

    Options for key derivation.

Returns:

  • (Wallet)

    A new Wallet instance.



47
48
49
50
51
# File 'lib/wallet/wallet.rb', line 47

def self.from_seed(seed, options = {})
  kp = KeyPairs::KeyPairs.new
  keys = kp.derive_key_pair(seed, options)
  new(keys[:public_key], keys[:private_key], seed: seed)
end

.generate(algorithm = 'secp256k1') ⇒ Wallet

Generates a new random wallet.

Parameters:

  • algorithm (String) (defaults to: 'secp256k1')

    The algorithm to use ('secp256k1' or 'ed25519').

Returns:

  • (Wallet)

    A new Wallet instance.



37
38
39
40
41
# File 'lib/wallet/wallet.rb', line 37

def self.generate(algorithm = 'secp256k1')
  kp = KeyPairs::KeyPairs.new
  seed = kp.generate_seed(nil, algorithm)
  from_seed(seed)
end

Instance Method Details

#get_x_address(tag: nil, test_network: false) ⇒ String

Derives the X-address for this wallet.

Parameters:

  • tag (Integer, false, nil) (defaults to: nil)

    The destination tag.

  • test_network (Boolean) (defaults to: false)

    Whether the address is for a test network.

Returns:

  • (String)

    The encoded X-address.



169
170
171
172
# File 'lib/wallet/wallet.rb', line 169

def get_x_address(tag: nil, test_network: false)
  address_codec = AddressCodec::AddressCodec.new
  address_codec.classic_address_to_x_address(@classic_address, tag, test_network)
end

#sign(transaction, multisign = false) ⇒ String, Hash

Signs a message (hex string) or transaction (Hash) with the wallet's private key.

Parameters:

  • transaction (String, Hash)

    The message (hex string) or transaction (Hash) to sign.

  • multisign (Boolean) (defaults to: false)

    Whether to sign for a multisigned transaction.

Returns:

  • (String, Hash)

    The signature (hex string) if a message was provided, or a hash containing :tx_blob and :hash if a transaction was provided.



68
69
70
71
72
73
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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/wallet/wallet.rb', line 68

def sign(transaction, multisign = false)
  algorithm = @algorithm
  # Check if message is a Hash (transaction)
  if transaction.is_a?(::Hash)
    prefix = multisign ? BinaryCodec::HASH_PREFIX[:transaction_multi_sig] : BinaryCodec::HASH_PREFIX[:transaction_sig]
    
    # 1. Prepare the transaction for signing
    tx_to_sign = transaction.dup
    if multisign
      # For multisigning, we need the SigningPubKey to be empty,
      # and we need to add the Signers field later.
      # We also need to add the Account of the signer to the signing data.
      tx_to_sign['SigningPubKey'] = ""
      # The multisign signing data MUST include the account of the signer.
    else
      tx_to_sign['SigningPubKey'] = @public_key
    end
    
    # Ensure SigningPubKey is serialized as a 0-length blob if empty
    
    signing_data = BinaryCodec.signing_data(tx_to_sign, prefix, signing_fields_only: true)
    
    if multisign
      # Append the account ID of the signer to the signing data
       = AddressCodec::AddressCodec.new.(@classic_address)
      signing_data += 
    end
    
    message = bytes_to_hex(signing_data)
    
    # 2. Sign the message
    signature = @key_pairs.sign(message, @private_key, algorithm)
    
    # 3. Create the signed transaction
    signed_tx = tx_to_sign.dup
    if multisign
      # For multisign=true, xrpl.js/PHP often returns a partially signed transaction
      # or an object containing the signature for a specific signer.
      # The PHP snippet expects a tx_blob.
      # Looking at the PHP snippet, it calls $wallet->sign($tx, true).
      # In XRPL, a multisigned transaction blob is usually the transaction
      # WITH the Signers array.
      
      signer = {
        "Signer" => {
          "Account" => @classic_address,
          "SigningPubKey" => @public_key,
          "TxnSignature" => signature
        }
      }
      signed_tx['Signers'] = [signer]
      signed_tx.delete('SigningPubKey') # Should be empty/not present in multisigned tx
    else
      signed_tx['TxnSignature'] = signature
    end
    
    # 4. Serialize the signed transaction
    tx_blob = BinaryCodec.json_to_binary(signed_tx)
    
    # 5. Generate the hash (transaction ID)
    # For transactions, the hash is SHA512Half of the serialized transaction with a prefix
    hash_prefix = [0x54, 0x58, 0x4E, 0x00].pack('C*') # 'TXN\0'
    hash = Digest::SHA512.digest(hash_prefix + [tx_blob].pack('H*'))[0...32].unpack1('H*').upcase
    
    return {
      'tx_blob' => tx_blob,
      'hash' => hash
    }
  end

  @key_pairs.sign(transaction, @private_key, algorithm)
end

#to_sString

Returns String representation of the wallet.

Returns:

  • (String)

    String representation of the wallet.



175
176
177
# File 'lib/wallet/wallet.rb', line 175

def to_s
  "Wallet(address: #{@classic_address}, public_key: #{@public_key})"
end

#verify(message, signature) ⇒ Boolean

Verifies a signature for a message.

Parameters:

  • message (String)

    The message as a hex string.

  • signature (String)

    The signature as a hex string.

Returns:

  • (Boolean)

    True if the signature is valid.



145
146
147
# File 'lib/wallet/wallet.rb', line 145

def verify(message, signature)
  @key_pairs.verify(message, signature, @public_key)
end

#verify_transaction(signed_transaction) ⇒ Boolean

Verifies a signed transaction blob.

Parameters:

  • signed_transaction (String)

    The signed transaction blob as a hex string.

Returns:

  • (Boolean)

    True if the transaction signature is valid.



152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/wallet/wallet.rb', line 152

def verify_transaction(signed_transaction)
  decoded = BinaryCodec.binary_to_json(signed_transaction)
  # The signing data is the transaction without the TxnSignature field,
  # prefixed by 0x53545800 (STX\0).
  
  tx_for_signing = decoded.dup
  signature = tx_for_signing.delete('TxnSignature')
  return false unless signature

  signing_data = BinaryCodec.signing_data(tx_for_signing)
  @key_pairs.verify(bytes_to_hex(signing_data), signature, @public_key)
end