Browse Source

support THORChain address generation

Example:

    $ mmgen-addrgen --coin=rune 1-10

Testing:

    $ test/tooltest2.py -q --coin=rune
    $ test/modtest.py -v bip_hd.multicoin
The MMGen Project 6 months ago
parent
commit
ba3b40c5b6

+ 1 - 0
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'),

+ 1 - 0
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',

+ 1 - 1
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):
 

+ 3 - 1
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

+ 1 - 1
mmgen/data/version

@@ -1 +1 @@
-15.1.dev37
+15.1.dev38

+ 63 - 0
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 <mmgen@tuta.io>
+# 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'

+ 22 - 0
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 <mmgen@tuta.io>
+# 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))

+ 2 - 1
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):

+ 2 - 0
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)
 

+ 2 - 0
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

+ 1 - 0
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):

+ 20 - 0
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
+}

+ 2 - 1
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="

+ 14 - 0
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': [