Skip to content

Commit b3f47f2

Browse files
authored
Merge pull request #31 from PIVX-Project/2020_cold-staking
[Core] Add Cold-Staking support
2 parents 8081b84 + 703d482 commit b3f47f2

File tree

11 files changed

+80
-35
lines changed

11 files changed

+80
-35
lines changed

img/icon_coldstaking.png

2.09 KB
Loading

src/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
MPATH_TESTNET = "44'/1'/"
1515
WIF_PREFIX = 212 # 212 = d4
1616
MAGIC_BYTE = 30
17+
STAKE_MAGIC_BYTE = 63
1718
TESTNET_WIF_PREFIX = 239
1819
TESTNET_MAGIC_BYTE = 139
20+
TESTNET_STAKE_MAGIC_BYTE = 73
1921
DEFAULT_PROTOCOL_VERSION = 70915
2022
MINIMUM_FEE = 0.0001 # minimum PIV/kB
2123
starting_width = 933

src/database.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ def initTables(self):
147147
cursor.execute("CREATE TABLE IF NOT EXISTS REWARDS("
148148
" tx_hash TEXT, tx_ouput_n INTEGER,"
149149
" satoshis INTEGER, confirmations INTEGER, script TEXT, mn_name TEXT, coinstake BOOLEAN,"
150+
" staker TEXT,"
150151
" PRIMARY KEY (tx_hash, tx_ouput_n))")
151152

152153
cursor.execute("CREATE TABLE IF NOT EXISTS RAWTXES("
@@ -476,6 +477,7 @@ def rewards_from_rows(self, rows):
476477
utxo['script'] = row[4]
477478
utxo['mn_name'] = row[5]
478479
utxo['coinstake'] = row[6]
480+
utxo['staker'] = row[7]
479481
# add to list
480482
rewards.append(utxo)
481483

@@ -489,9 +491,9 @@ def addReward(self, utxo):
489491
cursor = self.getCursor()
490492

491493
cursor.execute("INSERT OR REPLACE INTO REWARDS "
492-
"VALUES (?, ?, ?, ?, ?, ?, ?)",
494+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
493495
(utxo['txid'], utxo['vout'], utxo['satoshis'],
494-
utxo['confirmations'], utxo['script'], utxo['mn_name'], utxo['coinstake'])
496+
utxo['confirmations'], utxo['script'], utxo['mn_name'], utxo['coinstake'], utxo['staker'])
495497
)
496498

497499
except Exception as e:

src/hwdevice.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -128,16 +128,3 @@ def signMess(self, caller, path, message, isTestnet=False):
128128
printDbg("HW: Signing message...")
129129
self.api.signMess(caller, path, message, isTestnet)
130130
printOK("HW: Message signed")
131-
132-
133-
@check_api_init
134-
def signTxSign(self, ctrl):
135-
printDbg("HW: Signing TX...")
136-
self.api.signTxSign(ctrl)
137-
138-
139-
@check_api_init
140-
def signTxFinish(self):
141-
printDbg("HW: Finishing TX signature...")
142-
self.api.signTxFinish()
143-
printDbg("HW: TX signed")

src/ledgerClient.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ def append_inputs_to_TX(self, utxo, bip32_path):
148148
'pubkey': curr_pubkey,
149149
'bip32_path': bip32_path,
150150
'outputIndex': utxo['vout'],
151-
'txid': utxo['txid']
151+
'txid': utxo['txid'],
152+
'p2cs': (utxo['staker'] != "")
152153
})
153154

154155

@@ -370,7 +371,10 @@ def signTxSign(self, ctrl):
370371
inputTx.prevOut = bytearray.fromhex(new_input['txid'])[::-1] + int.to_bytes(new_input['outputIndex'], 4,
371372
byteorder='little')
372373

373-
inputTx.script = bytearray([len(sig)]) + sig + bytearray([0x21]) + new_input['pubkey']
374+
inputTx.script = bytearray([len(sig)]) + sig
375+
if new_input['p2cs']:
376+
inputTx.script += bytearray([0x00])
377+
inputTx.script += bytearray([0x21]) + new_input['pubkey']
374378

375379
inputTx.sequence = bytearray([0xFF, 0xFF, 0xFF, 0xFF])
376380

src/pivx_hashlib.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
import bitcoin
1010
import hashlib
1111

12-
from constants import WIF_PREFIX, MAGIC_BYTE, TESTNET_WIF_PREFIX, TESTNET_MAGIC_BYTE
12+
from constants import WIF_PREFIX, MAGIC_BYTE, TESTNET_WIF_PREFIX, TESTNET_MAGIC_BYTE, \
13+
STAKE_MAGIC_BYTE, TESTNET_STAKE_MAGIC_BYTE
1314
from pivx_b58 import b58encode, b58decode
1415

1516
def double_sha256(data):
@@ -42,16 +43,19 @@ def base58fromhex(hexstr, isTestnet):
4243
return b58encode(data + checksum)
4344

4445

45-
def pubkey_to_address(pubkey, isTestnet=False):
46+
def pubkey_to_address(pubkey, isTestnet=False, isCold=False):
4647
pubkey_bin = bytes.fromhex(pubkey)
4748
pkey_hash = bitcoin.bin_hash160(pubkey_bin)
48-
return pubkeyhash_to_address(pkey_hash, isTestnet)
49+
return pubkeyhash_to_address(pkey_hash, isTestnet, isCold)
4950

5051

5152

52-
def pubkeyhash_to_address(pkey_hash, isTestnet=False):
53-
base58_pubkey = TESTNET_MAGIC_BYTE if isTestnet else MAGIC_BYTE
54-
data = bytes([base58_pubkey]) + pkey_hash
53+
def pubkeyhash_to_address(pkey_hash, isTestnet=False, isCold=False):
54+
if isCold:
55+
base58_secret = TESTNET_STAKE_MAGIC_BYTE if isTestnet else STAKE_MAGIC_BYTE
56+
else:
57+
base58_secret = TESTNET_MAGIC_BYTE if isTestnet else MAGIC_BYTE
58+
data = bytes([base58_secret]) + pkey_hash
5559
checksum = bitcoin.bin_dbl_sha256(data)[0:4]
5660
return b58encode(data + checksum)
5761

src/pivx_parser.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
66

77
from misc import getCallerName, getFunctionName, printException
8-
from utils import extract_pkh_from_locking_script
8+
import utils
99
from pivx_hashlib import pubkeyhash_to_address
1010

1111
class HexParser():
@@ -72,9 +72,11 @@ def ParseTxOutput(p, isTestnet=False):
7272
vout["scriptPubKey"]["addresses"] = []
7373
try:
7474
locking_script = bytes.fromhex(vout["scriptPubKey"]["hex"])
75-
# add addresses only if P2PKH or P2PK
76-
if len(locking_script) in [25, 35]:
77-
add_bytes = extract_pkh_from_locking_script(locking_script)
75+
76+
# add addresses only if P2PKH, P2PK or P2CS
77+
if len(locking_script) in [25, 35, 51]:
78+
add_bytes = utils.extract_pkh_from_locking_script(locking_script)
79+
7880
address = pubkeyhash_to_address(add_bytes, isTestnet)
7981
vout["scriptPubKey"]["addresses"].append(address)
8082
except Exception as e:
@@ -102,6 +104,20 @@ def ParseTx(hex_string, isTestnet=False):
102104
return tx
103105

104106

105-
def IsCoinStake(rawtx):
106-
json_tx = ParseTx(rawtx)
107-
return json_tx['vout'][0]["scriptPubKey"]["hex"] == ""
107+
def IsCoinStake(tx):
108+
return tx['vout'][0]["scriptPubKey"]["hex"] == ""
109+
110+
111+
def IsPayToColdStaking(rawtx, out_n):
112+
tx = ParseTx(rawtx)
113+
script = tx['vout'][out_n]["scriptPubKey"]["hex"]
114+
return utils.IsPayToColdStaking(bytes.fromhex(script)), IsCoinStake(tx)
115+
116+
117+
def GetDelegatedStaker(rawtx, out_n, isTestnet):
118+
tx = ParseTx(rawtx)
119+
script = tx['vout'][out_n]["scriptPubKey"]["hex"]
120+
if not utils.IsPayToColdStaking(bytes.fromhex(script)):
121+
return ""
122+
pkh = utils.GetDelegatedStaker(bytes.fromhex(script))
123+
return pubkeyhash_to_address(pkh, isTestnet, isCold=True)

src/qt/gui_tabMain.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,5 @@ def loadIcons(self):
178178
self.ledgerImg = QPixmap(os.path.join(self.caller.imgDir, 'ledger.png'))
179179
self.trezorImg = QPixmap(os.path.join(self.caller.imgDir, 'trezorModT.png'))
180180
self.trezorOneImg = QPixmap(os.path.join(self.caller.imgDir, 'trezorOne.png'))
181+
self.coldStaking_icon = QIcon(os.path.join(self.caller.imgDir, 'icon_coldstaking.png'))
181182
self.threeDots_icon = QPixmap(os.path.join(self.caller.imgDir, 'icon_3dots.png'))

src/tabRewards.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from constants import MINIMUM_FEE
1616
from misc import printDbg, printError, printException, getCallerName, getFunctionName, \
1717
persistCacheSetting, myPopUp, myPopUp_sb, DisconnectedException, checkTxInputs
18-
from pivx_parser import ParseTx, IsCoinStake
18+
from pivx_parser import ParseTx, IsPayToColdStaking, GetDelegatedStaker
1919
from qt.gui_tabRewards import TabRewards_gui
2020
from threads import ThreadFuns
2121
from txCache import TxCache
@@ -100,6 +100,11 @@ def item(value):
100100
self.ui.rewardsList.box.setItem(row, 2, item(txId))
101101
self.ui.rewardsList.box.setItem(row, 3, item(str(utxo.get('vout', None))))
102102
self.ui.rewardsList.box.showRow(row)
103+
# mark cold utxos
104+
if utxo['staker'] != "":
105+
self.ui.rewardsList.box.item(row, 2).setIcon(self.caller.tabMain.coldStaking_icon)
106+
self.ui.rewardsList.box.item(row, 2).setToolTip("Staked by <b>%s</b>" % utxo['staker'])
107+
103108
# MARK COLLATERAL UTXO
104109
if txId == self.curr_txid:
105110
for i in range(0,4):
@@ -112,8 +117,9 @@ def item(value):
112117
if utxo['confirmations'] < required:
113118
for i in range(0,4):
114119
self.ui.rewardsList.box.item(row, i).setFlags(Qt.NoItemFlags)
120+
ttip = self.ui.rewardsList.box.item(row, i).toolTip()
115121
self.ui.rewardsList.box.item(row, i).setToolTip(
116-
"Immature - 100 confirmations required")
122+
ttip + "\n(Immature - %d confirmations required)" % required)
117123

118124
self.ui.rewardsList.box.resizeColumnsToContents()
119125

@@ -217,7 +223,10 @@ def load_utxos_thread(self, ctrl):
217223
mn_rewards[mn].remove(utxo)
218224
continue
219225
utxo['raw_tx'] = rawtx
220-
utxo['coinstake'] = IsCoinStake(rawtx)
226+
utxo['staker'] = ""
227+
p2cs, utxo['coinstake'] = IsPayToColdStaking(rawtx, utxo['vout'])
228+
if p2cs:
229+
utxo['staker'] = GetDelegatedStaker(rawtx, utxo['vout'], self.caller.isTestnetRPC)
221230
# Add utxo to database
222231
self.caller.parent.db.addReward(utxo)
223232

src/trezorClient.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ def __init__(self, model, main_wnd, *args, **kwargs):
8484

8585
@process_trezor_exceptions
8686
def append_inputs_to_TX(self, utxo, bip32_path, inputs):
87+
if utxo['staker'] != "":
88+
printException(getCallerName(), getFunctionName(), "Unable to sing P2CS on Trezor", "")
89+
return
8790
# Update amount
8891
self.amount += int(utxo['satoshis'])
8992
# Add input

0 commit comments

Comments
 (0)