{"id":34711018,"url":"https://github.com/m0wer/joinmarket_analyzer","last_synced_at":"2026-04-25T13:04:19.179Z","repository":{"id":326947896,"uuid":"1107221539","full_name":"m0wer/joinmarket_analyzer","owner":"m0wer","description":"Analyze JoinMarket Bitcoin CoinJoin transactions using ILP.","archived":false,"fork":false,"pushed_at":"2025-11-30T20:42:38.000Z","size":30,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-12-03T05:53:34.748Z","etag":null,"topics":["bitcoin","blockchain-analysis","coinjoin","joinmarket","privacy"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/m0wer.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-30T20:02:53.000Z","updated_at":"2025-11-30T20:57:28.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/m0wer/joinmarket_analyzer","commit_stats":null,"previous_names":["m0wer/joinmarket_analyzer"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/m0wer/joinmarket_analyzer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/m0wer%2Fjoinmarket_analyzer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/m0wer%2Fjoinmarket_analyzer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/m0wer%2Fjoinmarket_analyzer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/m0wer%2Fjoinmarket_analyzer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/m0wer","download_url":"https://codeload.github.com/m0wer/joinmarket_analyzer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/m0wer%2Fjoinmarket_analyzer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32262801,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-25T09:15:33.318Z","status":"ssl_error","status_checked_at":"2026-04-25T09:15:31.997Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bitcoin","blockchain-analysis","coinjoin","joinmarket","privacy"],"created_at":"2025-12-24T23:57:02.306Z","updated_at":"2026-04-25T13:04:19.173Z","avatar_url":"https://github.com/m0wer.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JoinMarket CoinJoin Analyzer\n\nAnalyze JoinMarket Bitcoin CoinJoin transactions. Using greedy pre-processing and Integer Linear Programming (ILP).\n\n## Features\n\n- **Deterministic preprocessing**: Greedy heuristic for unambiguous input-output matches\n- **Symmetry-breaking ILP**: Eliminates permutation duplicates, finds all distinct solutions\n- **Memory-safe**: 10GB limit enforcement\n- **Interrupt handling**: Ctrl+C saves progress\n- **Incremental saves**: Solutions written after each discovery\n- **Production-ready**: Modular architecture, comprehensive tests, CLI interface\n\n## Installation\n\n```bash\npip install -e .\n```\n\n## Usage\n\n### CLI\n\n```bash\n# Analyze a transaction\njoinmarket-analyze 0cb4870cf2dfa3877851088c673d163ae3c20ebcd6505c0be964d8fbcc856bbf\n\n# Custom parameters\njoinmarket-analyze \u003ctxid\u003e --max-fee-rel 0.1 --max-solutions 500\n\n# Help\njoinmarket-analyze --help\n\n# Output is automatically saved to solutions_\u003ctxid_prefix\u003e.json\n```\n\n### Scanner (Mass Analysis)\n\nThe `joinmarket-scan` tool allows you to scan blocks for JoinMarket transactions, analyze them, and store results in a SQLite database.\n\n```bash\n# Scan specific block range\njoinmarket-scan 924300 924305\n\n# Scan last 100 blocks\njoinmarket-scan -100\n\n# Resume scanning (skips already scanned blocks)\njoinmarket-scan 0\n\n# Check stats in the database\nsqlite3 joinmarket_stats.db \"SELECT count(*) FROM coinjointx;\"\n\n# Run in parallel (e.g., 4 jobs)\njoinmarket-scan 924300 924305 --jobs 4\n```\n\nThe scanner saves **partial results** if the exact solution cannot be found but the greedy algorithm successfully identifies the taker. This allows collecting fee statistics even for complex transactions.\n\n### Docker\n\n```bash\n# Run with a transaction ID\ndocker run --rm ghcr.io/m0wer/joinmarket_analyzer:master 0cb4870cf2dfa3877851088c673d163ae3c20ebcd6505c0be964d8fbcc856bbf\n\n# Run with memory limit (recommended)\ndocker run --rm -m 10g ghcr.io/m0wer/joinmarket_analyzer:master \u003ctxid\u003e --max-solutions 500\n\n# Using docker-compose\ndocker-compose run --rm joinmarket-analyzer \u003ctxid\u003e\n```\n\n### Python API\n\n```python\nfrom joinmarket_analyzer import analyze_transaction\n\nsolutions = analyze_transaction(\n    txid=\"0cb4870cf2dfa3877851088c673d163ae3c20ebcd6505c0be964d8fbcc856bbf\",\n    max_fee_rel=0.05,\n    max_solutions=1000\n)\n\nfor solution in solutions:\n    print(f\"Taker: Participant {solution.taker_index + 1}\")\n    print(f\"Maker fees: {solution.total_maker_fees:,} sats\")\n```\n\n### Working Example\n\n```bash\ndocker run --rm ghcr.io/m0wer/joinmarket_analyzer:master \\\n  0cb4870cf2dfa3877851088c673d163ae3c20ebcd6505c0be964d8fbcc856bbf \\\n  --max-fee-rel 0.001 --max-solutions 10\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eView Output\u003c/summary\u003e\n\n```\nTaker: Participant 4 (pays 21,368 sats)\n  💰 Participant 1 (maker)\n    Inputs: [0]\n    Outputs: Equal=6,357,366 sats, Change[2]=113,283,033 sats\n    Fee receives: 458 sats\n  💰 Participant 2 (maker)\n    Inputs: [1]\n    Outputs: Equal=6,357,366 sats, Change[6]=2,089,662,830 sats\n    Fee receives: 413 sats\n  💰 Participant 3 (maker)\n    Inputs: [3]\n    Outputs: Equal=6,357,366 sats, Change[19]=765,432,353 sats\n    Fee receives: 623 sats\n  🎯 Participant 4 (taker)\n    Inputs: [4]\n    Outputs: Equal=6,357,366 sats, No change output\n    Fee pays: 21,368 sats\n  💰 Participant 5 (maker)\n    Inputs: [5]\n    Outputs: Equal=6,357,366 sats, Change[11]=3,044,723 sats\n    Fee receives: 5,153 sats\n  💰 Participant 6 (maker)\n    Inputs: [6]\n    Outputs: Equal=6,357,366 sats, Change[3]=10,187,122 sats\n    Fee receives: 559 sats\n  💰 Participant 7 (maker)\n    Inputs: [7]\n    Outputs: Equal=6,357,366 sats, Change[8]=90,781,833 sats\n    Fee receives: 636 sats\n  💰 Participant 8 (maker)\n    Inputs: [8]\n    Outputs: Equal=6,357,366 sats, Change[12]=8,045,121 sats\n    Fee receives: 973 sats\n  💰 Participant 9 (maker)\n    Inputs: [9]\n    Outputs: Equal=6,357,366 sats, Change[7]=100,823,618 sats\n    Fee receives: 687 sats\n  💰 Participant 10 (maker)\n    Inputs: [11]\n    Outputs: Equal=6,357,366 sats, Change[20]=8,125,627 sats\n    Fee receives: 191 sats\n  💰 Participant 11 (maker)\n    Inputs: [2, 10]\n    Outputs: Equal=6,357,366 sats, Change[1]=87,861 sats\n    Fee receives: 693 sats\nTotal maker fees collected: 10,386 sats\nNetwork fee: 10,982 sats\n```\n\u003c/details\u003e\n\n## Algorithm\n\n### Greedy Preprocessing\n\n1. **Single-input matching** (iterative): Match inputs to change outputs where only one valid pairing exists\n2. **Multi-input fallback**: Sequential assignment for remaining inputs\n3. **Reduces search space**: Pre-assigns deterministic participants before ILP\n\n### ILP Formulation\n\n- **Variables**: Input assignments `x[i,p]`, change assignments `c[p,j]`, taker indicator `t[p]`\n- **Symmetry breaking**: Orders participants by minimum input index\n- **Partition cuts**: Excludes found solutions and all permutations\n- **Constraints**: Balance equations, fee bounds, dust thresholds, maker fee non-positivity\n\n## Testing\n\n```bash\n# Unit tests\npytest tests/unit/ -v\n\n# E2E tests (requires network)\npytest tests/e2e/ -v\n\n# All tests with coverage\npytest --cov=joinmarket_analyzer --cov-report=html\n```\n\n## Development\n\n```bash\n# Install with dev dependencies\npip install -e \".[dev]\"\n\n# Linting\npre-commit run --all-files\n```\n\n## Requirements\n\n- Python 3.9+\n- PuLP (CBC solver)\n- Pydantic 2.x\n- Loguru\n- Requests\n\n## Citation\n\nIf you use this tool in research, please cite:\n\n```\n@software{joinmarket_analyzer,\n  title = {JoinMarket CoinJoin Analyzer},\n  year = {2025},\n  url = {https://github.com/m0wer/joinmarket-analyzer}\n}\n```\n\n## Future Work \u0026 Research\n\nThis tool lays the groundwork for more advanced privacy research:\n\n- **Entropy Evaluation**: Measure how \"ambiguous\" change outputs are. If multiple valid solutions exist, the Taker is harder to pinpoint.\n- **Algorithm Design**: Evaluate and improve taker algorithms to intentionally create ambiguous change structures.\n- **Market Statistics**: Analyze historical CoinJoins to gather statistics on fee limits used by takers and earnings by makers.\n\n## Notes\n\n- Assumes JoinMarket protocol structure (equal outputs, optional change)\n- CBC solver timeout: 60s per iteration\n- Uses `mempool.sgn.space` API for transaction data (requires network access)\n\n## License\n\nMIT License\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fm0wer%2Fjoinmarket_analyzer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fm0wer%2Fjoinmarket_analyzer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fm0wer%2Fjoinmarket_analyzer/lists"}