Skip to content
Merged

80% #130

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions bsv/identity/contacts_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def _build_contact_tags(self, identity_key: Optional[str]) -> List[str]:

def _fetch_contact_outputs(self, tags: List[str], limit: int) -> List[Dict]:
"""Fetch contact outputs from wallet."""
outputs_result = self.wallet.list_outputs(None, {
outputs_result = self.wallet.list_outputs({
'basket': 'contacts',
'include': 'locking scripts',
'includeCustomInstructions': True,
Expand Down Expand Up @@ -146,7 +146,7 @@ def _decrypt_contact_output(self, output: Dict, pushdrop: PushDrop) -> Optional[
key_id = key_id_data.get('keyID')

ciphertext = decoded['fields'][0]
decrypt_result = self.wallet.decrypt(None, {
decrypt_result = self.wallet.decrypt({
'ciphertext': ciphertext,
'protocolID': CONTACT_PROTOCOL_ID,
'keyID': key_id,
Expand Down Expand Up @@ -198,7 +198,7 @@ def _hash_identity_key(self, identity_key: str) -> bytes:

def _find_existing_contact_output(self, hashed_key: bytes, key_id: str) -> tuple:
"""Find existing contact output if any."""
outputs_result = self.wallet.list_outputs(None, {
outputs_result = self.wallet.list_outputs({
'basket': 'contacts',
'include': 'entire transactions',
'includeCustomInstructions': True,
Expand Down Expand Up @@ -226,7 +226,7 @@ def _find_existing_contact_output(self, hashed_key: bytes, key_id: str) -> tuple
def _create_contact_locking_script(self, contact_to_store: Dict, key_id: str) -> str:
"""Create encrypted locking script for contact."""
contact_json = json.dumps(contact_to_store)
encrypt_result = self.wallet.encrypt(None, {
encrypt_result = self.wallet.encrypt({
'plaintext': contact_json.encode('utf-8'),
'protocolID': CONTACT_PROTOCOL_ID,
'keyID': key_id,
Expand All @@ -236,8 +236,8 @@ def _create_contact_locking_script(self, contact_to_store: Dict, key_id: str) ->
ciphertext = encrypt_result.get('ciphertext') or b''
pushdrop = PushDrop(self.wallet, None)
return pushdrop.lock(
None, [ciphertext], CONTACT_PROTOCOL_ID, key_id,
{'type': 0}, for_self=True, include_signature=True, lock_position='before'
[ciphertext], CONTACT_PROTOCOL_ID, key_id,
None, for_self=True, include_signature=True, lock_position='before'
)

def _save_or_update_contact_action(
Expand Down Expand Up @@ -297,7 +297,7 @@ def delete_contact(self, identity_key: str) -> None:
identity_key.encode('utf-8')
)

outputs_result = self.wallet.list_outputs(None, {
outputs_result = self.wallet.list_outputs({
'basket': 'contacts',
'include': 'entire transactions',
'tags': [f'identityKey {hashed_key.hex()}'],
Expand Down
26 changes: 12 additions & 14 deletions bsv/keystore/local_kv_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def _lookup_outputs_for_get(self, ctx: Any, key: str) -> Tuple[list, bytes]: #
args["counterparty"] = cpty
except Exception:
pass
lo = self._wallet.list_outputs(ctx, args, self._originator) or {}
lo = self._wallet.list_outputs(args, self._originator) or {}
outputs = lo.get("outputs") or []
beef_bytes = lo.get("BEEF") or b""
if not beef_bytes and outputs:
Expand Down Expand Up @@ -579,7 +579,7 @@ def _execute_set_operation(self, ctx: Any, key: str, value: str, ca_args: dict)
if ca_args and "use_woc" in ca_args:
create_args["use_woc"] = ca_args["use_woc"]

ca = self._wallet.create_action(ctx, create_args, self._originator) or {}
ca = self._wallet.create_action(create_args, self._originator) or {}
signable = (ca.get("signableTransaction") or {}) if isinstance(ca, dict) else {}
signable_tx_bytes = signable.get("tx") or b""

Expand All @@ -592,7 +592,7 @@ def _execute_set_operation(self, ctx: Any, key: str, value: str, ca_args: dict)
self._build_and_cache_beef(key, locking_script, tx_bytes)

# Broadcast and return result
self._wallet.internalize_action(ctx, {"tx": tx_bytes}, self._originator)
self._wallet.internalize_action({"tx": tx_bytes}, self._originator)
return self._extract_txid_from_bytes(tx_bytes, key)

def _build_and_cache_beef(self, key: str, locking_script: bytes, tx_bytes: bytes) -> None:
Expand Down Expand Up @@ -693,7 +693,7 @@ def _build_locking_script(self, ctx: Any, key: str, value: str, ca_args: dict =
},
"plaintext": value.encode('utf-8')
}
encrypt_result = self._wallet.encrypt(ctx, encrypt_args, self._originator)
encrypt_result = self._wallet.encrypt(encrypt_args, self._originator)
if "ciphertext" in encrypt_result:
field_bytes = encrypt_result["ciphertext"]
else:
Expand Down Expand Up @@ -722,7 +722,6 @@ def _build_locking_script(self, ctx: Any, key: str, value: str, ca_args: dict =
counterparty = ca_args.get("counterparty", pd_opts.get("counterparty"))
pd = PushDrop(self._wallet, self._originator)
return pd.lock(
ctx,
fields,
protocol_id,
key_id,
Expand Down Expand Up @@ -753,7 +752,7 @@ def _lookup_outputs_for_set(self, ctx: Any, key: str, ca_args: Optional[dict] =
args["key_id"] = kid
if cpty is not None:
args["counterparty"] = cpty
lo = self._wallet.list_outputs(ctx, args, self._originator) or {}
lo = self._wallet.list_outputs(args, self._originator) or {}
outs = [o for o in lo.get("outputs") or [] if not o.get("error")]
input_beef = lo.get("BEEF") or b""
if not input_beef and outs:
Expand Down Expand Up @@ -814,7 +813,6 @@ def _sign_and_relinquish_set(self, ctx: Any, key: str, outs: list, inputs_meta:
try:
spends_str_keys = {str(int(k)): v for k, v in (spends or {}).items()}
res = self._wallet.sign_action(
ctx,
{
"spends": spends_str_keys,
"reference": signable.get("reference") or b"",
Expand All @@ -826,7 +824,7 @@ def _sign_and_relinquish_set(self, ctx: Any, key: str, outs: list, inputs_meta:
except Exception:
for o in outs:
try:
self._wallet.relinquish_output(ctx, {
self._wallet.relinquish_output({
"basket": self._context,
"output": {
"txid": bytes.fromhex(o.get("txid", "00" * 32)) if isinstance(o.get("txid"), str) else (o.get("txid") or b"\x00" * 32),
Expand Down Expand Up @@ -871,7 +869,7 @@ def remove(self, ctx: Any, key: str) -> List[str]: # NOSONAR - Complexity (17),
self._release_key_lock(key)

def _lookup_outputs_for_remove(self, ctx: Any, key: str) -> Tuple[list, bytes, Optional[int]]:
lo = self._wallet.list_outputs(ctx, {
lo = self._wallet.list_outputs({
"basket": self._context,
"tags": [key],
"include": ENTIRE_TXS,
Expand All @@ -895,7 +893,7 @@ def _lookup_outputs_for_remove(self, ctx: Any, key: str) -> Tuple[list, bytes, O
return outs, input_beef, total_outputs

def _onchain_remove_flow(self, ctx: Any, key: str, inputs_meta: list, input_beef: bytes) -> Optional[str]:
ca_res = self._wallet.create_action(ctx, {
ca_res = self._wallet.create_action({
"labels": ["kv", "remove"],
"description": f"kvstore remove {key}",
"inputs": inputs_meta,
Expand All @@ -909,9 +907,9 @@ def _onchain_remove_flow(self, ctx: Any, key: str, inputs_meta: list, input_beef
reference = signable.get("reference") or b""
spends = self._prepare_spends(key, inputs_meta, signable_tx_bytes, input_beef)
spends_str = {str(int(k)): v for k, v in (spends or {}).items()}
res = self._wallet.sign_action(ctx, {"spends": spends_str, "reference": reference}, self._originator) or {}
res = self._wallet.sign_action({"spends": spends_str, "reference": reference}, self._originator) or {}
signed_tx_bytes = res.get("tx") if isinstance(res, dict) else None
internalize_result = self._wallet.internalize_action(ctx, {"tx": signed_tx_bytes or signable_tx_bytes}, self._originator)
internalize_result = self._wallet.internalize_action({"tx": signed_tx_bytes or signable_tx_bytes}, self._originator)
parsed_txid = None
try:
from bsv.transaction import Transaction
Expand Down Expand Up @@ -1017,7 +1015,7 @@ def _prepare_inputs_meta(self, key: str, outs: list, ca_args: dict = None) -> li

pd = PushDrop(self._wallet, self._originator)
unlock_protocol = protocol if protocol is not None else self._get_protocol(key)
unlocker = pd.unlock(unlock_protocol, key, {"type": 0}, sign_outputs='all')
unlocker = pd.unlock(unlock_protocol, key, None, sign_outputs='all') # None = SELF counterparty

inputs_meta = []
for o in outs:
Expand Down Expand Up @@ -1052,7 +1050,7 @@ def _prepare_spends(self, key, inputs_meta, signable_tx_bytes, input_beef): # N
pd = PushDrop(self._wallet, self._originator)
# Use default protocol for unlocking (GO pattern: protocol and key are separate)
unlock_protocol = self._get_protocol(key)
unlocker = pd.unlock(unlock_protocol, key, {"type": 0}, sign_outputs='all')
unlocker = pd.unlock(unlock_protocol, key, None, sign_outputs='all') # None = SELF counterparty
# Only prepare spends for inputs whose outpoint matches the tx input at the same index
for idx, meta in enumerate(inputs_meta):
try:
Expand Down
10 changes: 3 additions & 7 deletions bsv/registry/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def __init__(self, wallet: WalletInterface, originator: str = "registry-client")
self._resolver = LookupResolver()

def register_definition(self, ctx: Any, data: DefinitionData) -> Dict[str, Any]:
pub = self.wallet.get_public_key(ctx, {"identityKey": True}, self.originator) or {}
pub = self.wallet.get_public_key({"identityKey": True}, self.originator) or {}
operator = cast(str, pub.get("publicKey") or "")

_ = _map_definition_type_to_wallet_protocol(data.definitionType) # Reserved for future use
Expand All @@ -165,7 +165,6 @@ def register_definition(self, ctx: Any, data: DefinitionData) -> Dict[str, Any]:
# Create transaction
randomize_outputs = False
ca_res = self.wallet.create_action(
ctx,
{
"description": f"Register a new {data.definitionType} item",
"outputs": [
Expand All @@ -189,7 +188,6 @@ def list_own_registry_entries(self, ctx: Any, definition_type: DefinitionType) -
include_tags = True
include_labels = True
lo = self.wallet.list_outputs(
ctx,
{
"basket": _map_definition_type_to_basket_name(definition_type),
"include": "entire transactions",
Expand Down Expand Up @@ -239,7 +237,7 @@ def list_own_registry_entries(self, ctx: Any, definition_type: DefinitionType) -

def revoke_own_registry_entry(self, ctx: Any, record: Dict[str, Any]) -> Dict[str, Any]: # NOSONAR - Complexity (26), requires refactoring
# Owner check: ensure this wallet controls the registry operator key
me = self.wallet.get_public_key(ctx, {"identityKey": True}, self.originator) or {}
me = self.wallet.get_public_key({"identityKey": True}, self.originator) or {}
my_pub = cast(str, me.get("publicKey") or "")
operator = cast(str, record.get("registryOperator") or "")
if operator and my_pub and operator.lower() != my_pub.lower():
Expand All @@ -254,7 +252,6 @@ def revoke_own_registry_entry(self, ctx: Any, record: Dict[str, Any]) -> Dict[st

# Create partial transaction that spends the registry UTXO
ca_res = self.wallet.create_action(
ctx,
{
"description": f"Revoke {record.get('definitionType', 'registry')} item",
"inputBEEF": beef,
Expand Down Expand Up @@ -290,11 +287,10 @@ def revoke_own_registry_entry(self, ctx: Any, record: Dict[str, Any]) -> Dict[st
prev_satoshis=satoshis,
prev_locking_script=bytes.fromhex(cast(str, record.get("lockingScript", ""))) if record.get("lockingScript") else None,
)
unlocking_script = unlocker.sign(ctx, partial_tx, 0)
unlocking_script = unlocker.sign(partial_tx, 0)

spends = {0: {"unlockingScript": unlocking_script}}
sign_res = self.wallet.sign_action(
ctx,
{
"reference": reference,
"spends": spends,
Expand Down
1 change: 0 additions & 1 deletion bsv/registry/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ def __call__(self, ctx: Any, service_name: str, query: Dict[str, Any]) -> List[D

def query(self, ctx: Any, definition_type: DefinitionType, query: Dict[str, Any] = None) -> List[Dict[str, Any]]: # NOSONAR - query parameter reserved for future filtering capability
lo = self.wallet.list_outputs(
ctx,
{
"basket": _basket_name(definition_type),
"include": "entire transactions",
Expand Down
53 changes: 42 additions & 11 deletions bsv/transaction/pushdrop.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,12 +536,32 @@
max_len = 1 + 73 + 1
return (min_len, max_len)

def sign(self, tx, input_index: int) -> bytes: # noqa: D401

Check failure on line 539 in bsv/transaction/pushdrop.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 31 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=bsv-blockchain_py-sdk&issues=AZr8jpPH1eTA1jRIns6f&open=AZr8jpPH1eTA1jRIns6f&pullRequest=130
"""Create a signature for the given input using SIGHASH flags and return as pushdata.

Flags: base (ALL/NONE/SINGLE) derived from sign_outputs_mode, always includes FORKID,
and optionally ANYONECANPAY when anyone_can_pay is True.
"""
# Try to get locking script from transaction input if not already set
if not self.prev_locking_script and hasattr(tx, "inputs") and input_index < len(tx.inputs):
tin = tx.inputs[input_index]
if hasattr(tin, "source_transaction") and tin.source_transaction:
src_tx = tin.source_transaction
src_idx = getattr(tin, "source_output_index", 0)
if hasattr(src_tx, "outputs") and src_idx < len(src_tx.outputs):
out = src_tx.outputs[src_idx]
if hasattr(out, "locking_script"):
ls = out.locking_script
if hasattr(ls, "to_bytes"):
self.prev_locking_script = ls.to_bytes()
elif isinstance(ls, bytes):
self.prev_locking_script = ls
elif isinstance(ls, str):
try:
self.prev_locking_script = bytes.fromhex(ls)
except Exception:
pass

sighash_flag = self._compute_sighash_flag()
hash_to_sign, used_preimage = self._compute_hash_to_sign(tx, input_index, sighash_flag)

Expand Down Expand Up @@ -683,12 +703,17 @@
return None

print(f"[DEBUG] PushDropUnlocker.sign: Using locking public key from PushDrop UTXO: {locking_pubkey.hex()}")
# Use protocol_id/key_id/counterparty to derive the key (same as fallback)
# The derived key should match the locking public key if the protocol/key_id match
create_args = {
"encryption_args": {
"publicKey": locking_pubkey.hex(),
},
("hash_to_sign" if used_preimage else "data"): hash_to_sign,
"protocol_id": self.protocol_id,
"key_id": self.key_id,
"counterparty": self.counterparty,
}
if used_preimage:
create_args["hash_to_directly_sign"] = hash_to_sign
else:
create_args["data"] = hash_to_sign
res = self.wallet.create_signature(create_args, "") if hasattr(self.wallet, "create_signature") else {}
sig = res.get("signature", b"")
sig = bytes(sig) + bytes([sighash_flag])
Expand All @@ -699,18 +724,24 @@

def _create_fallback_signature(self, hash_to_sign: bytes, sighash_flag: int, used_preimage: bool) -> bytes:
"""Create signature using derived key (fallback method)."""
print("[DEBUG] PushDropUnlocker.sign: Fallback to derived public key")
print(f"[DEBUG] PushDropUnlocker.sign: Fallback to derived public key, protocol_id={self.protocol_id}, key_id={self.key_id}")
create_args = {
"encryption_args": {
"protocol_id": self.protocol_id,
"key_id": self.key_id,
"counterparty": self.counterparty,
},
("hash_to_sign" if used_preimage else "data"): hash_to_sign,
"protocol_id": self.protocol_id,
"key_id": self.key_id,
"counterparty": self.counterparty,
}
if used_preimage:
create_args["hash_to_directly_sign"] = hash_to_sign
else:
create_args["data"] = hash_to_sign
print(f"[DEBUG] PushDropUnlocker.sign: Calling create_signature with args: {create_args}")
res = self.wallet.create_signature(create_args, "") if hasattr(self.wallet, "create_signature") else {}
print(f"[DEBUG] PushDropUnlocker.sign: create_signature result: {res}")
sig = res.get("signature", b"")
if not sig:
print(f"[WARN] PushDropUnlocker.sign: No signature in result, result keys: {list(res.keys()) if isinstance(res, dict) else 'not a dict'}")
sig = bytes(sig) + bytes([sighash_flag])
print(f"[DEBUG] PushDropUnlocker.sign: Final sig length: {len(sig)}, sig: {sig.hex()[:100] if len(sig) > 0 else 'empty'}")
return encode_pushdata(sig)


Expand Down
Loading