diff --git a/mmgen/addr.py b/mmgen/addr.py index 40f0b451..01fda680 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -50,6 +50,7 @@ class MMGenAddrType(HiliteStr, InitErrors, MMGenObject): 'C': ati('compressed','std', True, 'p2pkh', 'p2pkh', 'wif', (), 'Compressed P2PKH address'), 'S': ati('segwit', 'std', True, 'segwit', 'p2sh', 'wif', (), 'Segwit P2SH-P2WPKH address'), 'B': ati('bech32', 'std', True, 'bech32', 'bech32', 'wif', (), 'Native Segwit (Bech32) address'), + 'X': ati('bech32x', 'std', True, 'p2pkh', 'bech32', 'wif', (), 'Cross-chain Bech32 address'), 'E': ati('ethereum', 'std', False,'ethereum','p2pkh', 'privkey', ('wallet_passwd',),'Ethereum address'), 'Z': ati('zcash_z','zcash_z',False,'zcash_z', 'zcash_z', 'wif', ('viewkey',), 'Zcash z-address'), 'M': ati('monero', 'monero', False,'monero', 'monero', 'spendkey',('viewkey','wallet_passwd'),'Monero address'), diff --git a/mmgen/addrgen.py b/mmgen/addrgen.py index c65aadfa..043a047d 100755 --- a/mmgen/addrgen.py +++ b/mmgen/addrgen.py @@ -59,6 +59,7 @@ def AddrGenerator(cfg, proto, addr_type): 'compressed': 'btc', 'segwit': 'btc', 'bech32': 'btc', + 'bech32x': 'xchain', 'monero': 'xmr', 'ethereum': 'eth', 'zcash_z': 'zec', diff --git a/mmgen/bip_hd/__init__.py b/mmgen/bip_hd/__init__.py index c01509ef..1f37d414 100644 --- a/mmgen/bip_hd/__init__.py +++ b/mmgen/bip_hd/__init__.py @@ -135,7 +135,7 @@ def check_privkey(key_int): class BipHDConfig(Lockable): - supported_coins = ('btc', 'eth', 'doge', 'ltc', 'bch') + supported_coins = ('btc', 'eth', 'doge', 'ltc', 'bch', 'rune') def __init__(self, base_cfg, coin, *, network, addr_type, from_path, no_path_checks): diff --git a/mmgen/bip_hd/chainparams.py b/mmgen/bip_hd/chainparams.py index 4ad09890..27500a9e 100644 --- a/mmgen/bip_hd/chainparams.py +++ b/mmgen/bip_hd/chainparams.py @@ -58,6 +58,8 @@ def parse_data(): return out +# RUNE derivation is SLIP-10, not BIP-44, but we treat them as equivalent + _data_in = """ [defaults] @@ -105,6 +107,7 @@ IDX CHAIN CURVE NW ADDR_CLS VB_PRV VB_PUB VB_WIF VB_ADDR DFL_PA 784 SUI edw m Sui x x - - 0'/0'/0' Sui 818 VET x m Eth x x - - x VeChain Token 888 NEO nist m Neo x x - spec x NEO +931 RUNE x m BechP2PKH x x - h:thor x THORChain 996 OKT x m Okex x x - - x OKChain Token 1023 ONE x m One x x - - x HARMONY-ONE (Legacy) 1024 ONT nist m Neo x x - spec x Ontology @@ -896,7 +899,6 @@ IDX CHAIN NAME 921 AVN - Avian Network 925 DIP - Dipper Network 928 GHM - HermitMatrixNetwork -931 RUNE - THORChain (RUNE) 941 --- - reserved 945 UNLOCK - Jasiri protocol 955 LTP - LifetionCoin diff --git a/mmgen/data/version b/mmgen/data/version index 8fc91924..efe4dff7 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -15.1.dev37 +15.1.dev38 diff --git a/mmgen/proto/rune/params.py b/mmgen/proto/rune/params.py new file mode 100755 index 00000000..609e60de --- /dev/null +++ b/mmgen/proto/rune/params.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# +# MMGen Wallet, a terminal-based cryptocurrency wallet +# Copyright (C)2013-2025 The MMGen Project +# Licensed under the GNU General Public License, Version 3: +# https://www.gnu.org/licenses +# Public project repositories: +# https://github.com/mmgen/mmgen-wallet +# https://gitlab.com/mmgen/mmgen-wallet + +""" +proto.rune.params: THORChain protocol +""" + +from ...protocol import CoinProtocol, decoded_addr, _nw +from ...addr import CoinAddr +from ...contrib import bech32 + +from ..btc.params import mainnet as btc_mainnet + +class mainnet(CoinProtocol.Secp256k1): + mod_clsname = 'THORChain' + network_names = _nw('mainnet', 'stagenet', 'testnet') + mmtypes = ('X',) + preferred_mmtypes = ('X',) + dfl_mmtype = 'X' + coin_amt = 'UniAmt' + max_tx_fee = 1 # TODO + caps = () + mmcaps = () + base_proto = 'THORChain' + base_proto_coin = 'RUNE' + base_coin = 'RUNE' + bech32_hrp = 'thor' + sign_mode = 'standalone' + avg_bdi = 6 # TODO + address_reuse_ok = False + + wif_ver_num = btc_mainnet.wif_ver_num + coin_cfg_opts = btc_mainnet.coin_cfg_opts + encode_wif = btc_mainnet.encode_wif + decode_wif = btc_mainnet.decode_wif + + def decode_addr(self, addr): + hrp, data = bech32.bech32_decode(addr) + assert hrp == self.bech32_hrp, f'{hrp!r}: invalid bech32 hrp (should be {self.bech32_hrp!r})' + return decoded_addr( + bytes(bech32.convertbits(data, 5, 8)), + None, + 'bech32') if data else False + + def encode_addr_bech32x(self, pubhash): + return CoinAddr( + self, + bech32.bech32_encode( + hrp = self.bech32_hrp, + data = bech32.convertbits(list(pubhash), 8, 5))) + +class stagenet(mainnet): + bech32_hrp = 'sthor' + +class testnet(stagenet): # testnet is deprecated + bech32_hrp = 'tthor' diff --git a/mmgen/proto/xchain/addrgen.py b/mmgen/proto/xchain/addrgen.py new file mode 100755 index 00000000..b291a48e --- /dev/null +++ b/mmgen/proto/xchain/addrgen.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# +# MMGen Wallet, a terminal-based cryptocurrency wallet +# Copyright (C)2013-2025 The MMGen Project +# Licensed under the GNU General Public License, Version 3: +# https://www.gnu.org/licenses +# Public project repositories: +# https://github.com/mmgen/mmgen-wallet +# https://gitlab.com/mmgen/mmgen-wallet + +""" +proto.xchain.addrgen: Cross-chain address generation classes for the MMGen suite +""" + +from ...addrgen import addr_generator, check_data +from ..btc.common import hash160 + +class bech32x(addr_generator.base): + + @check_data + def to_addr(self, data): + return self.proto.encode_addr_bech32x(hash160(data.pubkey)) diff --git a/mmgen/protocol.py b/mmgen/protocol.py index 402b6ebd..f1c957fc 100755 --- a/mmgen/protocol.py +++ b/mmgen/protocol.py @@ -46,7 +46,8 @@ class CoinProtocol(MMGenObject): 'eth': proto_info('Ethereum', 4), 'etc': proto_info('EthereumClassic', 4), 'zec': proto_info('Zcash', 2), - 'xmr': proto_info('Monero', 4) + 'xmr': proto_info('Monero', 4), + 'rune': proto_info('THORChain', 2) } class Base(Lockable): diff --git a/mmgen/tool/coin.py b/mmgen/tool/coin.py index 397dc709..2f0e3c94 100755 --- a/mmgen/tool/coin.py +++ b/mmgen/tool/coin.py @@ -165,6 +165,8 @@ class tool_cmd(tool_cmd_base): return self.proto.pubhash2segwitaddr(pubhash) elif self.mmtype.name == 'bech32': return self.proto.pubhash2bech32addr(pubhash) + elif self.mmtype.name == 'bech32x': + return self.proto.encode_addr_bech32x(pubhash) else: return self.proto.pubhash2addr(pubhash, self.mmtype.addr_fmt) diff --git a/setup.cfg b/setup.cfg index 6c46dc35..80465365 100644 --- a/setup.cfg +++ b/setup.cfg @@ -91,7 +91,9 @@ packages = mmgen.proto.eth.tx mmgen.proto.eth.tw mmgen.proto.ltc + mmgen.proto.rune mmgen.proto.secp256k1 + mmgen.proto.xchain mmgen.proto.xmr mmgen.proto.zec mmgen.swap diff --git a/test/modtest_d/bip_hd.py b/test/modtest_d/bip_hd.py index f7361660..b5c72f75 100755 --- a/test/modtest_d/bip_hd.py +++ b/test/modtest_d/bip_hd.py @@ -146,6 +146,7 @@ vectors_multicoin = { 'bch_compressed': 'bitcoincash:qpqpcllprftg4s0chdgkpxhxv23wfymq3gj7n0a9vw', 'bsc_smart': '0x373731f4d885Fc7Da05498F9f0804a87A14F891b', 'bnb_beacon': 'bnb179c3ymltqm4utlp089zxqeta5dvn48a305rhe5', + 'rune': 'thor1nr6fye3nznyn20m5w6fey6w8a8l4q599cdqmpc', } def wif2addr(cfg, wif): diff --git a/test/ref/thorchain/98831F3A-RUNE-X[1,31-33,500-501,1010-1011].addrs b/test/ref/thorchain/98831F3A-RUNE-X[1,31-33,500-501,1010-1011].addrs new file mode 100644 index 00000000..a0acc42f --- /dev/null +++ b/test/ref/thorchain/98831F3A-RUNE-X[1,31-33,500-501,1010-1011].addrs @@ -0,0 +1,20 @@ +# MMGen address file +# +# This file is editable. +# Everything following a hash symbol '#' is a comment and ignored by MMGen. +# A text label of 80 screen cells or less may be added to the right of each +# address, and it will be appended to the tracking wallet label upon import. +# The label may contain any printable ASCII symbol. +# +# Address data checksum for 98831F3A-RUNE-X[1,31-33,500-501,1010-1011]: 00C6 1930 557F 5E99 +# Record this value to a secure location. +98831F3A RUNE:BECH32X { + 1 thor137aqwpky5muxw7rvrdy6kxegt838k04xp4rnge + 31 thor1w758u285uqv5h3wuksylvxtahql9tjsdllurug + 32 thor18srpqhu8q8ppp4zc3zje3yp5lf3jpmq8hu4phh + 33 thor16hgd6g3cutx8dhslvdgxljss4w7prq32u9x20y + 500 thor14q7m5jyajjnlqgccu3vh2dx0m2323558faxlz6 + 501 thor1gyaexk8l3rvt7vsfd693y33ate989r64ak65gk + 1010 thor1g95knu2elqt8l0d5vcyf0hyuspn4mhsjrym8g9 + 1011 thor12f6lgkjy7amxdz7eznv33xypvzux9kxslkqvzq +} diff --git a/test/test-release.d/cfg.sh b/test/test-release.d/cfg.sh index 29ecfbc4..351ab352 100755 --- a/test/test-release.d/cfg.sh +++ b/test/test-release.d/cfg.sh @@ -299,9 +299,10 @@ init_tests() { e $tooltest2_py --coin=eth --token=mm1 e $tooltest2_py --coin=eth --token=mm1 --testnet=1 e $tooltest2_py --coin=etc + t $tooltest2_py --coin=rune - $tooltest2_py --fork # run once with --fork so commands are actually executed " - [ "$SKIP_ALT_DEP" ] && t_tool2_skip='a e' # skip ETH,ETC: txview requires py_ecc + [ "$SKIP_ALT_DEP" ] && t_tool2_skip='a e t' # skip ETH,ETC: txview requires py_ecc d_tool="'mmgen-tool' utility (all supported coins)" t_tool=" diff --git a/test/tooltest2_d/data.py b/test/tooltest2_d/data.py index aa8c2ed7..673f33bc 100755 --- a/test/tooltest2_d/data.py +++ b/test/tooltest2_d/data.py @@ -110,6 +110,8 @@ eth_pubhex2 = '9166c289b9f905e55f9e3df9f69d7f356b4a22095f894f4715714aa4b56606aff xmr_pubhex1 = '1ed49357e217e79dab3c5503822f2bdb561e302e24476ee6ff33242c7551d4e78944790c0cfa9998c2f196061be89b2b8387f9d397db20ea8e049899cdc947d1' zec_pubhex1 = 'e6a4edbff547f21bcc2a825b6cf70f06e266a452d2da9d6dc5c1da3d99d7e996f488704dcdfe8d92cafe47772b3f692a98d59de1e99e00ff815f64ae59910f0c' +rune_addr1 = 'thor1xptlvmwaymaxa7pxkr2u5pn7c0508stcr9tw2z' + tests = { 'Mnemonic': { 'hex2mn': ( @@ -407,6 +409,9 @@ tests = { ([btc_addr5], pubhash1), ([btc_addr6], pubhash2), ], + 'rune_mainnet': [ + ([rune_addr1], pubhash2), + ], }, 'eth_checksummed_addr': { 'eth_mainnet': [ @@ -423,6 +428,9 @@ tests = { ['--type=segwit'], 'segwit'), ([pubhash2], btc_addr6, ['--type=bech32'], 'bech32'), ], + 'rune_mainnet': [ + ([pubhash2], rune_addr1, ['--type=X'], 'bech32x'), + ], }, 'addr2scriptpubkey': { 'btc_mainnet': [ @@ -665,6 +673,12 @@ tests = { 'E97A D796 B495 E8BC' ), ], + 'rune_mainnet': [ + ( + ['test/ref/thorchain/98831F3A-RUNE-X[1,31-33,500-501,1010-1011].addrs'], + '00C6 1930 557F 5E99' + ), + ], }, 'viewkeyaddrfile_chksum': { 'xmr_mainnet': [