@@ -161,23 +161,110 @@ class BinanceMainnet( cryptocurrencies.Cryptocurrency ):
161161 WIF_SECRET_KEY = 0x80
162162
163163
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.
165167
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...)
167253 addresses use standard BIP44 derivation.
168254
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 |
181268
182269 """
183270 CRYPTO_NAMES = dict ( # Currently supported (in order of visibility)
@@ -187,9 +274,10 @@ class Account( hdwallet.HDWallet ):
187274 dogecoin = 'DOGE' ,
188275 cronos = 'CRO' ,
189276 binance = 'BNB' ,
277+ ripple = 'XRP' ,
190278 )
191279 CRYPTOCURRENCIES = set ( CRYPTO_NAMES .values () )
192- CRYPTOCURRENCIES_BETA = set ( ('BNB' , 'CRO' ) )
280+ CRYPTOCURRENCIES_BETA = set ( ('BNB' , 'CRO' , 'XRP' ) )
193281
194282 ETHJS_ENCRYPT = set ( ('ETH' , 'CRO' , 'BNB' ) ) # Can be encrypted w/ Ethereum JSON wallet
195283 BIP38_ENCRYPT = CRYPTOCURRENCIES - ETHJS_ENCRYPT # Can be encrypted w/ BIP-38
@@ -201,13 +289,18 @@ class Account( hdwallet.HDWallet ):
201289 DOGE = "legacy" ,
202290 CRO = "bech32" ,
203291 BNB = "legacy" ,
292+ XRP = "legacy" ,
204293 )
205294
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+ )
208300 CRYPTO_LOCAL = dict (
209301 CRO = CronosMainnet ,
210302 BNB = BinanceMainnet ,
303+ XRP = RippleMainnet ,
211304 )
212305 CRYPTO_LOCAL_SYMBOL = dict (
213306 BNB = "ETH"
@@ -237,7 +330,10 @@ class Account( hdwallet.HDWallet ):
237330 bech32 = "m/84'/2'/0'/0/0" ,
238331 ),
239332 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" ,
241337 )
242338 )
243339
@@ -285,14 +381,16 @@ def supported( cls, crypto ):
285381
286382 def __init__ ( self , crypto , format = None ):
287383 crypto = Account .supported ( crypto )
288- cryptocurrency = self .CRYPTO_LOCAL .get ( crypto , None ) # None, unless locally defined, above
384+ cryptocurrency = self .CRYPTO_LOCAL .get ( crypto )
289385 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 :
295392 raise ValueError ( f"{ crypto } does not support address format { self .format } " )
393+ self .hdwallet = hdwallet_cls ( symbol = crypto , cryptocurrency = cryptocurrency )
296394
297395 def from_seed ( self , seed : str , path : str = None ) -> "Account" :
298396 """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":
305403 self .from_path ( path )
306404 return self
307405
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+
308415 def from_path ( self , path : str = None ) -> "Account" :
309416 """Change the Account to derive from the provided path.
310417
@@ -376,6 +483,10 @@ def path( self ):
376483 def key ( self ):
377484 return self .hdwallet .private_key ()
378485
486+ @property
487+ def pubkey ( self ):
488+ return self .hdwallet .public_key ()
489+
379490 def from_private_key ( self , private_key ):
380491 self .hdwallet .from_private_key ( private_key )
381492 return self
0 commit comments