auth checks
This commit is contained in:
parent
531ae613c7
commit
00d8c841f7
@ -791,7 +791,8 @@ class ApproveTransaction(UserAuthorizedAction):
|
||||
async def interact(self):
|
||||
# Prompt user w/ details and get approval
|
||||
from glob import dis, hsm_active
|
||||
from ccc import CCCFeature, PolicyViolationException
|
||||
from ccc import CCCFeature
|
||||
from exceptions import CCCPolicyViolationError
|
||||
|
||||
# step 1: parse PSBT from PSRAM into in-memory objects.
|
||||
|
||||
@ -826,23 +827,12 @@ class ApproveTransaction(UserAuthorizedAction):
|
||||
|
||||
dis.progress_bar_show(0.85)
|
||||
|
||||
if CCCFeature.is_enabled():
|
||||
if self.psbt.active_multisig and (ccc_c_xfp in self.psbt.active_multisig.xfp_paths):
|
||||
CCCFeature.validate_tx(self.psbt)
|
||||
self.psbt.sign_it(CCCFeature.get_encoded_secret(), ccc_c_xfp)
|
||||
|
||||
except FraudulentChangeOutput as exc:
|
||||
print('FraudulentChangeOutput: ' + exc.args[0])
|
||||
return await self.failure(exc.args[0], title='Change Fraud')
|
||||
except FatalPSBTIssue as exc:
|
||||
print('FatalPSBTIssue: ' + exc.args[0])
|
||||
return await self.failure(exc.args[0])
|
||||
except PolicyViolationException as exc:
|
||||
ch = await ux_show_story(
|
||||
exc.args[0]+"\n\n Would you like to sign with A key?",
|
||||
title='Policy Violation')
|
||||
if ch != "y":
|
||||
return
|
||||
except BaseException as exc:
|
||||
del self.psbt
|
||||
gc.collect()
|
||||
@ -855,6 +845,10 @@ class ApproveTransaction(UserAuthorizedAction):
|
||||
|
||||
return await self.failure(msg, exc)
|
||||
|
||||
# early test for spending policy; not an error if violates policy
|
||||
# - might add warnings
|
||||
could_ccc_sign, needs_2fa = CCCFeature.could_sign(self.psbt)
|
||||
|
||||
# step 2: figure out what we are approving, so we can get sign-off
|
||||
# - outputs, amounts
|
||||
# - fee
|
||||
@ -954,7 +948,7 @@ class ApproveTransaction(UserAuthorizedAction):
|
||||
return await self.failure(msg)
|
||||
|
||||
if ch not in 'yb':
|
||||
# they don't want to!
|
||||
# they don't want to sign!
|
||||
self.refused = True
|
||||
|
||||
await ux_dramatic_pause("Refused.", 1)
|
||||
@ -964,11 +958,27 @@ class ApproveTransaction(UserAuthorizedAction):
|
||||
self.done()
|
||||
return
|
||||
|
||||
if needs_2fa and could_ccc_sign:
|
||||
# They still need to pass web2fa challenge (but it meets other specs ok)
|
||||
try:
|
||||
await CCCFeature.web2fa_challenge()
|
||||
except:
|
||||
could_ccc_sign = False
|
||||
ch = await ux_show_story("Will not add CCC signature. Proceed anyway?")
|
||||
if ch != 'y':
|
||||
return await self.failure("2FA Failed")
|
||||
|
||||
# do the actual signing.
|
||||
try:
|
||||
dis.fullscreen('Wait...')
|
||||
gc.collect() # visible delay caused by this but also sign_it() below
|
||||
self.psbt.sign_it()
|
||||
|
||||
if could_ccc_sign:
|
||||
dis.fullscreen('CCC Sign...')
|
||||
gc.collect()
|
||||
CCCFeature.sign_psbt(self.psbt)
|
||||
|
||||
except FraudulentChangeOutput as exc:
|
||||
return await self.failure(exc.args[0], title='Change Fraud')
|
||||
except MemoryError:
|
||||
|
||||
@ -10,11 +10,9 @@ from menu import MenuSystem, MenuItem
|
||||
from seed import seed_words_to_encoded_secret
|
||||
from stash import SecretStash, len_to_numwords
|
||||
from charcodes import KEY_QR, KEY_CANCEL, KEY_NFC
|
||||
from exceptions import CCCPolicyViolationError
|
||||
|
||||
|
||||
class PolicyViolationException(Exception):
|
||||
pass
|
||||
|
||||
class CCCFeature:
|
||||
@classmethod
|
||||
def is_enabled(cls):
|
||||
@ -44,9 +42,7 @@ class CCCFeature:
|
||||
def get_xfp(cls):
|
||||
# just the XFP
|
||||
ccc = settings.get('ccc')
|
||||
if ccc:
|
||||
return ccc['c_xfp']
|
||||
|
||||
return ccc['c_xfp'] if ccc else None
|
||||
|
||||
@classmethod
|
||||
def init_setup(cls, words):
|
||||
@ -103,15 +99,79 @@ class CCCFeature:
|
||||
settings.save()
|
||||
|
||||
@classmethod
|
||||
def validate_tx(cls, psbt):
|
||||
policy = cls.get_policy()
|
||||
magnitude = policy.get("mag", None)
|
||||
async def meets_policy(cls, psbt):
|
||||
# Does policy allow signing this? Else raise why
|
||||
pol = cls.get_policy()
|
||||
|
||||
# mag
|
||||
magnitude = pol.get("mag", None)
|
||||
outgoing = psbt.total_value_out - psbt.total_change_value
|
||||
if magnitude < 1000:
|
||||
# it is a BTC, convert to sats
|
||||
magnitude = magnitude * 100000000
|
||||
|
||||
if outgoing > magnitude:
|
||||
raise PolicyViolationException("magnitude")
|
||||
raise CCCPolicyViolationError("magnitude")
|
||||
|
||||
# vel
|
||||
|
||||
# whitelist
|
||||
|
||||
# web2fa
|
||||
# - slow, requires UX, and they might not acheive it...
|
||||
# - wait until about to do signature
|
||||
if pol.get('web2fa', False):
|
||||
psbt.warnings.append(('CCC', 'Web 2FA required.'))
|
||||
return True
|
||||
|
||||
|
||||
@classmethod
|
||||
def could_sign(cls, psbt):
|
||||
# We are looking at a PSBT: can we sign it, and would we?
|
||||
# - if we **could** but will not, due to policy, add warning msg
|
||||
# - return (we could sign, needs2fa step)
|
||||
if not cls.is_enabled:
|
||||
return False, False
|
||||
|
||||
ms = psbt.active_multisig
|
||||
if not ms:
|
||||
# single-sig CCC not supported
|
||||
return False, False
|
||||
|
||||
xfp = cls.get_xfp()
|
||||
if (xfp not in ms.xfp_paths):
|
||||
# does not involve us
|
||||
return False, False
|
||||
|
||||
try:
|
||||
# check policy
|
||||
needs_2fa = cls.meets_policy(psbt)
|
||||
except CCCPolicyViolationError:
|
||||
psbt.warning.append(('CCC', "Violates spending policy. Won't sign."))
|
||||
return False, False
|
||||
|
||||
return True, needs_2fa
|
||||
|
||||
@classmethod
|
||||
async def web2fa_challenge(cls):
|
||||
# they are trying to sign something, so make them get our their phone
|
||||
# - at this point they have already ok'ed the details of the txn
|
||||
# - and we have approved other elements of the spending policy.
|
||||
# - TODO: maybe show wallet name? or even txn details?? (but info leak to Coinkite)
|
||||
pol = cls.get_policy()
|
||||
ss = pol.get('web2fa')
|
||||
assert ss
|
||||
|
||||
ok = await web2fa.perform_web2fa('Approve CCC Transaction', ss)
|
||||
if not ok:
|
||||
raise CCCPolicyViolationError
|
||||
|
||||
@classmethod
|
||||
def sign_psbt(cls, psbt):
|
||||
# do the math
|
||||
# TODO: capture the block height if vel is defined; no going back after this pt.
|
||||
psbt.sign_it(cls.get_encoded_secret(), cls.get_xfp())
|
||||
|
||||
|
||||
def render_mag_value(mag):
|
||||
# handle integer bitcoins, and satoshis in same value
|
||||
@ -544,6 +604,7 @@ async def gen_or_import():
|
||||
elif ch == '6':
|
||||
# pick existing from Seed Vault
|
||||
enc = await SeedVaultChooserMenu.pick(words_only=True)
|
||||
if not enc: return None
|
||||
words = SecretStash.decode_words(enc)
|
||||
await enable_step1(words)
|
||||
|
||||
|
||||
@ -51,4 +51,8 @@ class QRDecodeExplained(ValueError):
|
||||
class UnknownAddressExplained(ValueError):
|
||||
pass
|
||||
|
||||
# We're not going to co-sign using CCC feature
|
||||
class CCCPolicyViolationError(RuntimeError):
|
||||
pass
|
||||
|
||||
# EOF
|
||||
|
||||
Loading…
Reference in New Issue
Block a user