mirror of
https://github.com/SeedSigner/seedsigner.git
synced 2026-09-14 04:45:08 +00:00
Mark coordinator-supplied change data as claimed
Nothing in a psbt proves the fingerprints and derivation paths it carries, but change_data's fingerprint and derivation_path keys read like facts -- the change details view splits one into a wallet path and hands it straight to get_xpub. Renaming them to claimed_fingerprint and claimed_derivation_path, along with the locals that carry them, puts the trust boundary in the data itself rather than in the logic around it. The class docstring now states the convention behind the prefix: claimed_ for coordinator-supplied metadata, verified_ for what this device proved by re-deriving from the signing seed, and the invariant that no verified_ value comes from a claimed_ one without a derivation in between. It also records that change_data covers self-transfers, not just change-branch outputs. No logic changes; the two dict keys are the only behavioral difference.
This commit is contained in:
@@ -27,10 +27,30 @@ class PSBTParser():
|
||||
as change, the fee, where the spend is going, and any OP_RETURN payload.
|
||||
|
||||
Constructing it with a seed parses immediately. The results are read off the instance
|
||||
attributes, with per-change-output detail in change_data.
|
||||
attributes, with per-output detail in change_data. Note that change_data and
|
||||
change_amount cover every output coming back to this seed, self-transfers to a receive
|
||||
address included, not just the ones on a change branch. The view layer tells the two
|
||||
apart by the branch index in the derivation path.
|
||||
|
||||
has_matching_input_fingerprint answers the earlier question of which seed a psbt is
|
||||
for and needs no parse.
|
||||
|
||||
A psbt is written by an untrusted coordinator. The metadata it carries about keys
|
||||
(fingerprints, derivation paths, xpubs) is a claim, not a fact. The onus is on us to
|
||||
verify by re-deriving from the signing seed. For multisig, verification depends on the
|
||||
user providing a "known good" descriptor (i.e. can be trusted) from which we can
|
||||
verify the outputs by deriving from the cosigners' xpubs.
|
||||
|
||||
This class makes the difference visible in its own names:
|
||||
|
||||
claimed_... coordinator-supplied metadata (fingerprints, derivation paths, xpubs).
|
||||
Safe to read and display; never safe to make a decision on.
|
||||
verified_... a fact this device proved by re-deriving from the signing seed and
|
||||
matching real key material. Only assigned by code that performed that
|
||||
derivation.
|
||||
|
||||
Invariant: no verified_ value is ever assigned from a claimed_ value without an
|
||||
intervening re-derivation from self.root.
|
||||
"""
|
||||
|
||||
# Upper bound on how many levels of derivation a single parse will cache. 1000 is
|
||||
@@ -237,27 +257,27 @@ class PSBTParser():
|
||||
|
||||
elif is_change:
|
||||
addr = vout[i].script_pubkey.address(NETWORKS[SettingsConstants.map_network_to_embit(self.network)])
|
||||
fingerprints = []
|
||||
derivation_paths = []
|
||||
claimed_fingerprints = []
|
||||
claimed_derivation_paths = []
|
||||
|
||||
# extract info from non-taproot outputs
|
||||
if len(self.psbt.outputs[i].bip32_derivations) > 0:
|
||||
for d, derivation_path in self.psbt.outputs[i].bip32_derivations.items():
|
||||
fingerprints.append(hexlify(derivation_path.fingerprint).decode())
|
||||
derivation_paths.append(bip32.path_to_str(derivation_path.derivation))
|
||||
claimed_fingerprints.append(hexlify(derivation_path.fingerprint).decode())
|
||||
claimed_derivation_paths.append(bip32.path_to_str(derivation_path.derivation))
|
||||
|
||||
# extract info from taproot outputs
|
||||
if len(self.psbt.outputs[i].taproot_bip32_derivations) > 0:
|
||||
for d, (leaf_hashes, derivation) in self.psbt.outputs[i].taproot_bip32_derivations.items():
|
||||
fingerprints.append(hexlify(derivation.fingerprint).decode())
|
||||
derivation_paths.append(bip32.path_to_str(derivation.derivation))
|
||||
claimed_fingerprints.append(hexlify(derivation.fingerprint).decode())
|
||||
claimed_derivation_paths.append(bip32.path_to_str(derivation.derivation))
|
||||
|
||||
self.change_data.append({
|
||||
"output_index": i,
|
||||
"address": addr,
|
||||
"amount": vout[i].value,
|
||||
"fingerprint": fingerprints,
|
||||
"derivation_path": derivation_paths,
|
||||
"claimed_fingerprint": claimed_fingerprints,
|
||||
"claimed_derivation_path": claimed_derivation_paths,
|
||||
})
|
||||
self.change_amount += vout[i].value
|
||||
|
||||
|
||||
@@ -116,16 +116,16 @@ class PSBTOverviewView(View):
|
||||
{
|
||||
'address': 'bc1q............',
|
||||
'amount': 397621401,
|
||||
'fingerprint': ['22bde1a9', '73c5da0a'],
|
||||
'derivation_path': ['m/48h/1h/0h/2h/1/0', 'm/48h/1h/0h/2h/1/0']
|
||||
'claimed_fingerprint': ['22bde1a9', '73c5da0a'],
|
||||
'claimed_derivation_path': ['m/48h/1h/0h/2h/1/0', 'm/48h/1h/0h/2h/1/0']
|
||||
}, {},
|
||||
]
|
||||
"""
|
||||
num_change_outputs = 0
|
||||
num_self_transfer_outputs = 0
|
||||
for change_output in change_data:
|
||||
# print(f"""{change_output["derivation_path"][0]}""")
|
||||
if change_output["derivation_path"][0].split("/")[-2] == "1":
|
||||
# print(f"""{change_output["claimed_derivation_path"][0]}""")
|
||||
if change_output["claimed_derivation_path"][0].split("/")[-2] == "1":
|
||||
num_change_outputs += 1
|
||||
else:
|
||||
num_self_transfer_outputs += 1
|
||||
@@ -325,8 +325,8 @@ class PSBTChangeDetailsView(View):
|
||||
{
|
||||
'address': 'bc1q............',
|
||||
'amount': 397621401,
|
||||
'fingerprint': ['22bde1a9', '73c5da0a'],
|
||||
'derivation_path': ['m/48h/1h/0h/2h/1/0', 'm/48h/1h/0h/2h/1/0']
|
||||
'claimed_fingerprint': ['22bde1a9', '73c5da0a'],
|
||||
'claimed_derivation_path': ['m/48h/1h/0h/2h/1/0', 'm/48h/1h/0h/2h/1/0']
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -334,16 +334,16 @@ class PSBTChangeDetailsView(View):
|
||||
# and derivation path.
|
||||
seed_fingerprint = self.controller.psbt_seed.get_fingerprint(self.settings.get_value(SettingsConstants.SETTING__NETWORK))
|
||||
|
||||
if seed_fingerprint not in change_data.get("fingerprint"):
|
||||
if seed_fingerprint not in change_data.get("claimed_fingerprint"):
|
||||
# TODO: Something is wrong with this psbt(?). Reroute to warning?
|
||||
return Destination(NotYetImplementedView)
|
||||
|
||||
i = change_data.get("fingerprint").index(seed_fingerprint)
|
||||
derivation_path = change_data.get("derivation_path")[i]
|
||||
i = change_data.get("claimed_fingerprint").index(seed_fingerprint)
|
||||
claimed_derivation_path = change_data.get("claimed_derivation_path")[i]
|
||||
|
||||
# 'm/84h/1h/0h/1/0' would be a change addr while 'm/84h/1h/0h/0/0' is a self-receive
|
||||
is_change_derivation_path = int(derivation_path.split("/")[-2]) == 1
|
||||
derivation_path_addr_index = int(derivation_path.split("/")[-1])
|
||||
is_change_derivation_path = int(claimed_derivation_path.split("/")[-2]) == 1
|
||||
derivation_path_addr_index = int(claimed_derivation_path.split("/")[-1])
|
||||
|
||||
if is_change_derivation_path:
|
||||
# TRANSLATOR_NOTE: The amount you're receiving back from the transaction
|
||||
@@ -384,8 +384,8 @@ class PSBTChangeDetailsView(View):
|
||||
script_type = pubkey.script_type()
|
||||
|
||||
# extract derivation path to get wallet and change derivation
|
||||
change_path = '/'.join(derivation_path.split("/")[-2:])
|
||||
wallet_path = '/'.join(derivation_path.split("/")[:-2])
|
||||
change_path = '/'.join(claimed_derivation_path.split("/")[-2:])
|
||||
wallet_path = '/'.join(claimed_derivation_path.split("/")[:-2])
|
||||
|
||||
xpub = self.controller.psbt_seed.get_xpub(
|
||||
wallet_path=wallet_path,
|
||||
@@ -425,7 +425,7 @@ class PSBTChangeDetailsView(View):
|
||||
amount=change_data.get("amount"),
|
||||
is_multisig=psbt_parser.is_multisig,
|
||||
fingerprint=seed_fingerprint,
|
||||
derivation_path=derivation_path,
|
||||
derivation_path=claimed_derivation_path,
|
||||
is_change_derivation_path=is_change_derivation_path,
|
||||
derivation_path_addr_index=derivation_path_addr_index,
|
||||
is_change_addr_verified=is_change_addr_verified,
|
||||
|
||||
@@ -321,8 +321,8 @@ def test_p2tr_change_detection():
|
||||
'output_index': 0,
|
||||
'address': 'bcrt1prz4g6saush37epdwhvwpu78td3q7yfz3xxz37axlx7udck6wracq3rwq30',
|
||||
'amount': 2871443918,
|
||||
'fingerprint': ['394aed14'],
|
||||
'derivation_path': ['m/86h/1h/0h/1/1']}
|
||||
'claimed_fingerprint': ['394aed14'],
|
||||
'claimed_derivation_path': ['m/86h/1h/0h/1/1']}
|
||||
]
|
||||
assert pp.spend_amount == 319049328
|
||||
assert pp.change_amount == 2871443918
|
||||
@@ -481,8 +481,8 @@ def test_parse_op_return_content():
|
||||
'output_index': 0,
|
||||
'address': 'bcrt1qvwkhakqhz7m7kmz6332avatsmdy32m644g86vv',
|
||||
'amount': 99992296,
|
||||
'fingerprint': ['0fb882ff'],
|
||||
'derivation_path': ["m/84h/1h/0h/0/2"]}
|
||||
'claimed_fingerprint': ['0fb882ff'],
|
||||
'claimed_derivation_path': ["m/84h/1h/0h/0/2"]}
|
||||
]
|
||||
assert psbt_parser.spend_amount == 0 # This is a self-spend; no value being spent, other than the tx fee
|
||||
assert psbt_parser.change_amount == 99992296
|
||||
|
||||
Reference in New Issue
Block a user