@@ -161,23 +161,110 @@ class BinanceMainnet( cryptocurrencies.Cryptocurrency ):
161
161
WIF_SECRET_KEY = 0x80
162
162
163
163
164
- class Account ( hdwallet .HDWallet ):
164
+ class RippleMainnet ( cryptocurrencies .Cryptocurrency ):
165
+ """The standard HDWallet.p2pkh_address (Pay to Public Key Hash) encoding is used, w/ a prefix of
166
+ 00. However, the XRP-specific base-58 encoding is used, resulting in a fixed 'r' prefix.
165
167
166
- """Supports producing Legacy addresses for Bitcoin, and Litecoin. Doge (D...) and Ethereum (0x...)
168
+ See: https://xrpl.org/accounts.html#address-encoding.
169
+
170
+ """
171
+ NAME = "Ripple"
172
+ SYMBOL = "XRP"
173
+ NETWORK = "mainnet"
174
+ SOURCE_CODE = "https://github.com/ripple/rippled"
175
+ COIN_TYPE = cryptocurrencies .CoinType ({
176
+ "INDEX" : 144 ,
177
+ "HARDENED" : True
178
+ })
179
+
180
+ PUBLIC_KEY_ADDRESS = 0x00 # Results in the prefix r..., when used w/ the Ripple base-58 alphabet
181
+ SEGWIT_ADDRESS = cryptocurrencies .SegwitAddress ({
182
+ "HRP" : None ,
183
+ "VERSION" : 0x00
184
+ })
185
+
186
+ EXTENDED_PRIVATE_KEY = cryptocurrencies .ExtendedPrivateKey ({
187
+ "P2PKH" : None ,
188
+ "P2SH" : None ,
189
+ "P2WPKH" : None ,
190
+ "P2WPKH_IN_P2SH" : None ,
191
+ "P2WSH" : None ,
192
+ "P2WSH_IN_P2SH" : None ,
193
+ })
194
+ EXTENDED_PUBLIC_KEY = cryptocurrencies .ExtendedPublicKey ({
195
+ "P2PKH" : None ,
196
+ "P2SH" : None ,
197
+ "P2WPKH" : None ,
198
+ "P2WPKH_IN_P2SH" : None ,
199
+ "P2WSH" : None ,
200
+ "P2WSH_IN_P2SH" : None ,
201
+ })
202
+
203
+ MESSAGE_PREFIX = None
204
+ DEFAULT_PATH = f"m/44'/{ str (COIN_TYPE )} /0'/0/0"
205
+ WIF_SECRET_KEY = 0x80
206
+
207
+
208
+ class XRPHDWallet ( hdwallet .HDWallet ) :
209
+ """The XRP address format uses the standard p2pkh_address formulation, from
210
+ https://xrpl.org/accounts.html#creating-accounts:
211
+
212
+ The ripemd160 hash of sha256 hash of public key, then base58-encoded w/ 4-byte checksum. The
213
+ base-58 dictionary used is the standard Ripple (not Bitcoin!) alphabet:
214
+
215
+ rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz
216
+
217
+ NOTE: Only secp256k1 keypairs are supported; these are the default for the Ripple ledger.
218
+
219
+ """
220
+ def p2pkh_address ( self ):
221
+ p2pkh_btc = super ( XRPHDWallet , self ).p2pkh_address ()
222
+ p2pkh = base58 .b58decode_check ( p2pkh_btc )
223
+ return base58 .b58encode_check ( p2pkh , base58 .RIPPLE_ALPHABET ).decode ( 'UTF-8' )
224
+
225
+
226
+ class Account :
227
+ """A Cryptocurrency "Account" / Wallet, based on a variety of underlying Python crypto-asset
228
+ support modules. Presently, only meherett/python-hdwallet is used
229
+
230
+ An appropriate hdwallet-like wrapper is built, for any crypto-asset supported using another
231
+ module. The required hdwallet API calls are:
232
+
233
+ .from_seed -- start deriving from the provided seed
234
+ .from_mnemonic -- start deriving from the provided seed via BIP-39 mnemonic
235
+ .clean_derivation -- forget any prior derivation path
236
+ .from_path -- derive a wallet from the specified derivation path
237
+ .p2pkh_address -- produce a Legacy format address
238
+ .p2sh_address -- produce a SegWit format address
239
+ .p2wpkh_address -- produce a Bech32 format address
240
+ .path -- return the current wallet derivation path
241
+ .private_key -- return the current wallet's private key
242
+
243
+ For testing eg. BIP-38 encrypted wallets:
244
+
245
+ .from_private_key -- import a specific private key
246
+ .from_encrypted -- import an encrypted wallet
247
+
248
+ Also expect the following attributes to be available:
249
+
250
+ ._cryptocurrency.SYMBOL: The short name of the crypto-asset, eg 'XRP'
251
+
252
+ Supports producing Legacy addresses for Bitcoin, and Litecoin. Doge (D...) and Ethereum (0x...)
167
253
addresses use standard BIP44 derivation.
168
254
169
- | Crypto | Semantic | Path | Address | Support |
170
- |--------+----------+------------------+---------+---------|
171
- | ETH | Legacy | m/44'/60'/0'/0/0 | 0x... | |
172
- | BNB | Legacy | m/44'/60'/0'/0/0 | 0x... | Beta |
173
- | CRO | Bech32 | m/44'/60'/0'/0/0 | crc1... | Beta |
174
- | BTC | Legacy | m/44'/ 0'/0'/0/0 | 1... | |
175
- | | SegWit | m/44'/ 0'/0'/0/0 | 3... | |
176
- | | Bech32 | m/84'/ 0'/0'/0/0 | bc1... | |
177
- | LTC | Legacy | m/44'/ 2'/0'/0/0 | L... | |
178
- | | SegWit | m/44'/ 2'/0'/0/0 | M... | |
179
- | | Bech32 | m/84'/ 2'/0'/0/0 | ltc1... | |
180
- | DOGE | Legacy | m/44'/ 3'/0'/0/0 | D... | |
255
+ | Crypto | Semantic | Path | Address | Support |
256
+ |--------+----------+-------------------+---------+---------|
257
+ | ETH | Legacy | m/44'/ 60'/0'/0/0 | 0x... | |
258
+ | BNB | Legacy | m/44'/ 60'/0'/0/0 | 0x... | Beta |
259
+ | CRO | Bech32 | m/44'/ 60'/0'/0/0 | crc1... | Beta |
260
+ | BTC | Legacy | m/44'/ 0'/0'/0/0 | 1... | |
261
+ | | SegWit | m/44'/ 0'/0'/0/0 | 3... | |
262
+ | | Bech32 | m/84'/ 0'/0'/0/0 | bc1... | |
263
+ | LTC | Legacy | m/44'/ 2'/0'/0/0 | L... | |
264
+ | | SegWit | m/44'/ 2'/0'/0/0 | M... | |
265
+ | | Bech32 | m/84'/ 2'/0'/0/0 | ltc1... | |
266
+ | DOGE | Legacy | m/44'/ 3'/0'/0/0 | D... | |
267
+ | XRP | Legacy | m/44'/144'/0'/0/0 | r... | Beta |
181
268
182
269
"""
183
270
CRYPTO_NAMES = dict ( # Currently supported (in order of visibility)
@@ -187,9 +274,10 @@ class Account( hdwallet.HDWallet ):
187
274
dogecoin = 'DOGE' ,
188
275
cronos = 'CRO' ,
189
276
binance = 'BNB' ,
277
+ ripple = 'XRP' ,
190
278
)
191
279
CRYPTOCURRENCIES = set ( CRYPTO_NAMES .values () )
192
- CRYPTOCURRENCIES_BETA = set ( ('BNB' , 'CRO' ) )
280
+ CRYPTOCURRENCIES_BETA = set ( ('BNB' , 'CRO' , 'XRP' ) )
193
281
194
282
ETHJS_ENCRYPT = set ( ('ETH' , 'CRO' , 'BNB' ) ) # Can be encrypted w/ Ethereum JSON wallet
195
283
BIP38_ENCRYPT = CRYPTOCURRENCIES - ETHJS_ENCRYPT # Can be encrypted w/ BIP-38
@@ -201,13 +289,18 @@ class Account( hdwallet.HDWallet ):
201
289
DOGE = "legacy" ,
202
290
CRO = "bech32" ,
203
291
BNB = "legacy" ,
292
+ XRP = "legacy" ,
204
293
)
205
294
206
- # Any locally-defined python-hdwallet cryptocurrencies, and any that may require some
207
- # adjustments when calling python-hdwallet address and other functions.
295
+ # Any locally-defined python-hdwallet classes, cryptocurrency definitions, and any that may
296
+ # require some adjustments when calling python-hdwallet address and other functions.
297
+ CRYPTO_WALLET_CLS = dict (
298
+ XRP = XRPHDWallet ,
299
+ )
208
300
CRYPTO_LOCAL = dict (
209
301
CRO = CronosMainnet ,
210
302
BNB = BinanceMainnet ,
303
+ XRP = RippleMainnet ,
211
304
)
212
305
CRYPTO_LOCAL_SYMBOL = dict (
213
306
BNB = "ETH"
@@ -237,7 +330,10 @@ class Account( hdwallet.HDWallet ):
237
330
bech32 = "m/84'/2'/0'/0/0" ,
238
331
),
239
332
DOGE = dict (
240
- legacy = "m/44'/3'/0'/0/0" ,
333
+ legacy = "m/44'/3'/0'/0/0" ,
334
+ ),
335
+ XRP = dict (
336
+ legacy = "m/44'/144'/0'/0/0" ,
241
337
)
242
338
)
243
339
@@ -285,14 +381,16 @@ def supported( cls, crypto ):
285
381
286
382
def __init__ ( self , crypto , format = None ):
287
383
crypto = Account .supported ( crypto )
288
- cryptocurrency = self .CRYPTO_LOCAL .get ( crypto , None ) # None, unless locally defined, above
384
+ cryptocurrency = self .CRYPTO_LOCAL .get ( crypto )
289
385
self .format = format .lower () if format else Account .address_format ( crypto )
290
- if self .format in ("legacy" , "segwit" ,):
291
- self .hdwallet = hdwallet .BIP44HDWallet ( symbol = crypto , cryptocurrency = cryptocurrency )
292
- elif self .format in ("bech32" ,):
293
- self .hdwallet = hdwallet .BIP84HDWallet ( symbol = crypto , cryptocurrency = cryptocurrency )
294
- else :
386
+ hdwallet_cls = self .CRYPTO_WALLET_CLS .get ( crypto )
387
+ if hdwallet_cls is None and self .format in ("legacy" , "segwit" ,):
388
+ hdwallet_cls = hdwallet .BIP44HDWallet
389
+ if hdwallet_cls is None and self .format in ("bech32" ,):
390
+ hdwallet_cls = hdwallet .BIP84HDWallet
391
+ if hdwallet_cls is None :
295
392
raise ValueError ( f"{ crypto } does not support address format { self .format } " )
393
+ self .hdwallet = hdwallet_cls ( symbol = crypto , cryptocurrency = cryptocurrency )
296
394
297
395
def from_seed ( self , seed : str , path : str = None ) -> "Account" :
298
396
"""Derive the Account from the supplied seed and (optionally) path; uses the default derivation path
@@ -305,6 +403,15 @@ def from_seed( self, seed: str, path: str = None ) -> "Account":
305
403
self .from_path ( path )
306
404
return self
307
405
406
+ def from_mnemonic ( self , mnemonic : str , path : str = None ) -> "Account" :
407
+ """Derive the Account from the supplied BIP-39 mnemonic and (optionally) path; uses the
408
+ default derivation path for the Account address format, if None provided.
409
+
410
+ """
411
+ self .hdwallet .from_mnemonic ( mnemonic )
412
+ self .from_path ( path )
413
+ return self
414
+
308
415
def from_path ( self , path : str = None ) -> "Account" :
309
416
"""Change the Account to derive from the provided path.
310
417
@@ -376,6 +483,10 @@ def path( self ):
376
483
def key ( self ):
377
484
return self .hdwallet .private_key ()
378
485
486
+ @property
487
+ def pubkey ( self ):
488
+ return self .hdwallet .public_key ()
489
+
379
490
def from_private_key ( self , private_key ):
380
491
self .hdwallet .from_private_key ( private_key )
381
492
return self
0 commit comments