Skip to content
Merged
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
34 changes: 34 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: CodeQL

on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
- cron: '0 0 * * 1'

permissions:
contents: read
security-events: write
actions: read

jobs:
analyze:
name: Analyze (python)
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: python
queries: security-and-quality

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: CodeQL
3 changes: 0 additions & 3 deletions .github/workflows/osv-scanner.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@ on:
- '.github/workflows/osv-scanner.yml'
pull_request:
branches: [main]
paths:
- 'requirements.txt'
- 'dev-requirements.txt'
schedule:
# Run weekly on Monday at 00:00 UTC to catch new CVEs
- cron: '0 0 * * 1'
Expand Down
62 changes: 61 additions & 1 deletion cryptnox_cli/command/card/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@
from ...config import get_configuration
from ...wallet import eth
from ...wallet.btc import BTCwallet, BlkHubApi
from ...wallet import xrp as xrp_wallet
except ImportError:
import enums
from config import get_configuration
from wallet import eth
from wallet.btc import BTCwallet, BlkHubApi
from wallet import xrp as xrp_wallet

__all__ = ['Cards']

Expand All @@ -43,7 +45,12 @@ def execute(card) -> int:
print("Gathering information from the network...")
eth_info = Info._get_eth_info(card)

Info._print_info_table([Info._get_btc_info(card), eth_info])
Info._print_info_table([
Info._get_btc_info(card),
eth_info,
Info._get_xrp_info(card),
Info._get_bnb_info(card),
])

config = get_configuration(card)
if not config["eth"]["api_key"] and config["eth"]["endpoint"] == "infura":
Expand Down Expand Up @@ -117,6 +124,59 @@ def _get_eth_info(card) -> dict:

return tabulate_data

@staticmethod
def _get_xrp_info(card) -> dict:
tabulate_data = {"name": "XRP", "network": "mainnet", "balance": "--"}
try:
pubkey = card.get_public_key(
cryptnox_sdk_py.Derivation.DERIVE,
path=xrp_wallet.PATH
)
tabulate_data["address"] = xrp_wallet.address(pubkey)
except Exception as error:
print(f"There's an issue in retrieving XRP address: {error}")
tabulate_data["address"] = "Error"
return tabulate_data

try:
balance = xrp_wallet.get_balance(tabulate_data["address"])
tabulate_data["balance"] = f"{balance} XRP"
except Exception as error:
print(f"There's an issue in retrieving XRP balance: {error}")
tabulate_data["balance"] = "Network issue"

return tabulate_data

@staticmethod
def _get_bnb_info(card) -> dict:
# BNB on Binance Smart Chain (BSC) is EVM-compatible: same secp256k1
# curve, same derivation path, and same address format as Ethereum.
config = get_configuration(card)["eth"]
try:
derivation = cryptnox_sdk_py.Derivation[config["derivation"]].value
except KeyError:
return {"name": "BNB", "address": "Bad derivation type", "network": "BSC mainnet"}

tabulate_data = {"name": "BNB", "network": "BSC mainnet", "balance": "--"}
try:
path = "" if derivation == cryptnox_sdk_py.Derivation.CURRENT_KEY else eth.Api.PATH
public_key = card.get_public_key(derivation, path=path, compressed=False)
tabulate_data["address"] = eth.checksum_address(public_key)
except Exception as error:
print(f"There's an issue in retrieving BNB address: {error}")
tabulate_data["address"] = "Error"
return tabulate_data

try:
w3 = web3.Web3(web3.Web3.HTTPProvider("https://bsc-dataseed.binance.org/"))
balance_wei = w3.eth.get_balance(tabulate_data["address"])
tabulate_data["balance"] = f"{web3.Web3.from_wei(balance_wei, 'ether')} BNB"
except Exception as error:
print(f"There's an issue in retrieving BNB balance: {error}")
tabulate_data["balance"] = "Network issue"

return tabulate_data

@staticmethod
def _print_info_table(info: List[Dict]) -> None:
print("\n")
Expand Down
3 changes: 2 additions & 1 deletion cryptnox_cli/command/options/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ def _btc_options(subparsers, interactive_mode):
def add_send(sub_parser):
def _validate(address: str) -> str:
if re.match('^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$', address) or \
re.match('^[2nm][a-km-zA-HJ-NP-Z1-9]{25,34}$', address):
re.match('^[2nm][a-km-zA-HJ-NP-Z1-9]{25,34}$', address) or \
re.match('^(bc1|tb1)[a-zA-HJ-NP-Z0-9]{25,62}$', address):
return address
raise argparse.ArgumentTypeError("Not a valid BTC address")

Expand Down
2 changes: 2 additions & 0 deletions cryptnox_cli/wallet/btc.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,8 @@ def test_addr(btc_addr: str):
if btc_addr.startswith("n") or btc_addr.startswith(
"m") or btc_addr.startswith("2"):
return re.match('^[2nm][a-km-zA-HJ-NP-Z1-9]{25,34}$', btc_addr)
if btc_addr.startswith("bc1") or btc_addr.startswith("tb1"):
return re.match('^(bc1|tb1)[a-zA-HJ-NP-Z0-9]{25,62}$', btc_addr)

return False

Expand Down
76 changes: 76 additions & 0 deletions cryptnox_cli/wallet/xrp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
"""
XRP address derivation and balance lookup from secp256k1 public key.

XRP uses the same secp256k1 curve as Bitcoin. The address is derived by:
1. SHA-256 hash of the compressed public key
2. RIPEMD-160 of the SHA-256 result (Account ID)
3. Base58Check encoding with version byte 0x00 using the XRP alphabet
"""
import hashlib

import base58
import requests

PATH = "m/44'/144'/0'/0/0"

# XRP uses a different Base58 alphabet than Bitcoin
XRP_ALPHABET = b'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz'

MAINNET_RPC = "https://s1.ripple.com:51234/"

# 1 XRP = 1 000 000 drops
_DROPS_PER_XRP = 1_000_000


def address(public_key_hex: str) -> str:
"""
Derive an XRP classic address (r-address) from a compressed secp256k1 public key.

:param str public_key_hex: Compressed public key as a hex string (33 bytes / 66 hex chars)
:return: XRP address starting with 'r'
:rtype: str
"""
public_key_bytes = bytes.fromhex(public_key_hex)

sha256_hash = hashlib.sha256(public_key_bytes).digest()

ripemd160 = hashlib.new('ripemd160')
ripemd160.update(sha256_hash)
account_id = ripemd160.digest()

# Version byte 0x00 for XRP classic addresses
return base58.b58encode_check(b'\x00' + account_id, alphabet=XRP_ALPHABET).decode()


def get_balance(xrp_address: str) -> float:
"""
Return the XRP balance for *xrp_address* in XRP (not drops).

Returns 0.0 for accounts that exist on-chain but have not yet been funded
(actNotFound). Raises an exception for any other network or protocol error.

:param str xrp_address: Classic r-address (e.g. 'rHb9CJA...')
:return: Balance in XRP
:rtype: float
:raises requests.RequestException: On HTTP / connection failures
:raises ValueError: On unexpected ledger error responses
"""
payload = {
"method": "account_info",
"params": [{"account": xrp_address, "ledger_index": "current"}],
}
response = requests.post(MAINNET_RPC, json=payload, timeout=10)
response.raise_for_status()

result = response.json().get("result", {})

# Account exists but has never been funded – treat as zero balance
if result.get("error") == "actNotFound":
return 0.0

if result.get("status") == "error":
raise ValueError(result.get("error_message", "XRP network error"))

drops = int(result["account_data"]["Balance"])
return drops / _DROPS_PER_XRP
Loading