proof-of-portfolio 0.0.81__tar.gz → 0.0.82__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/PKG-INFO +1 -1
  2. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/_version.py +1 -1
  3. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/proof_generator.py +147 -249
  4. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio.egg-info/PKG-INFO +1 -1
  5. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/pyproject.toml +1 -1
  6. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/README.md +0 -0
  7. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/__init__.py +0 -0
  8. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/analyze_data.py +0 -0
  9. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/Nargo.toml +0 -0
  10. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/Nargo.toml +0 -0
  11. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/core/calmar.nr +0 -0
  12. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/core/drawdown.nr +0 -0
  13. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/core/merkle.nr +0 -0
  14. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/core/mod.nr +0 -0
  15. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/core/omega.nr +0 -0
  16. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/core/pnl_score.nr +0 -0
  17. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/core/position.nr +0 -0
  18. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/core/sharpe.nr +0 -0
  19. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/core/sortino.nr +0 -0
  20. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/core/tstat.nr +0 -0
  21. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/lib.nr +0 -0
  22. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/utils/ann_excess_return.nr +0 -0
  23. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/utils/ann_volatility.nr +0 -0
  24. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/utils/average.nr +0 -0
  25. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/utils/constants.nr +0 -0
  26. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/utils/mod.nr +0 -0
  27. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/utils/sqrt.nr +0 -0
  28. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/utils/variance.nr +0 -0
  29. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/components/src/utils/weighting_distribution.nr +0 -0
  30. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/generate_inputs.py +0 -0
  31. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/src/main.nr +0 -0
  32. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/target/circuits.json +0 -0
  33. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/circuits/vk/vk +0 -0
  34. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_calmar/Nargo.toml +0 -0
  35. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_calmar/src/main.nr +0 -0
  36. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_calmar/target/just_calmar.json +0 -0
  37. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_cps_to_log_return/Nargo.toml +0 -0
  38. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_cps_to_log_return/src/main.nr +0 -0
  39. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_cps_to_log_return/target/just_cps_to_log_return.json +0 -0
  40. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_drawdown/Nargo.toml +0 -0
  41. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_drawdown/src/main.nr +0 -0
  42. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_drawdown/target/just_drawdown.json +0 -0
  43. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_omega/Nargo.toml +0 -0
  44. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_omega/src/main.nr +0 -0
  45. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_omega/target/just_omega.json +0 -0
  46. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_pnl/Nargo.toml +0 -0
  47. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_pnl/src/main.nr +0 -0
  48. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_sharpe/Nargo.toml +0 -0
  49. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_sharpe/src/main.nr +0 -0
  50. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_sharpe/target/just_sharpe.json +0 -0
  51. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_sortino/Nargo.toml +0 -0
  52. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_sortino/src/main.nr +0 -0
  53. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_sortino/target/just_sortino.json +0 -0
  54. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_tstat/Nargo.toml +0 -0
  55. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_tstat/src/main.nr +0 -0
  56. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/just_tstat/target/just_tstat.json +0 -0
  57. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/merkle_generator/Nargo.toml +0 -0
  58. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demo/merkle_generator/src/main.nr +0 -0
  59. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demos/all.py +0 -0
  60. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demos/calmar.py +0 -0
  61. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demos/drawdown.py +0 -0
  62. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demos/generate_input_data.py +0 -0
  63. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demos/log_returns.py +0 -0
  64. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demos/main.py +0 -0
  65. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demos/omega.py +0 -0
  66. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demos/sharpe.py +0 -0
  67. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demos/sortino.py +0 -0
  68. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demos/tstat.py +0 -0
  69. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/demos/utils.py +0 -0
  70. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/main.py +0 -0
  71. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/min_metrics.py +0 -0
  72. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/miner.py +0 -0
  73. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/parsing_utils.py +0 -0
  74. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/post_install.py +0 -0
  75. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/returns_generator/Nargo.toml +0 -0
  76. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/returns_generator/src/main.nr +0 -0
  77. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/returns_generator/target/returns_generator.json +0 -0
  78. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/signal_processor.py +0 -0
  79. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/tree_generator/Nargo.toml +0 -0
  80. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/tree_generator/src/main.nr +0 -0
  81. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/tree_generator/target/tree_generator.json +0 -0
  82. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/tree_generator/target.gz +0 -0
  83. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio/validator.py +0 -0
  84. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio.egg-info/SOURCES.txt +0 -0
  85. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio.egg-info/dependency_links.txt +0 -0
  86. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio.egg-info/entry_points.txt +0 -0
  87. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio.egg-info/requires.txt +0 -0
  88. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/proof_of_portfolio.egg-info/top_level.txt +0 -0
  89. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/setup.cfg +0 -0
  90. {proof_of_portfolio-0.0.81 → proof_of_portfolio-0.0.82}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proof-of-portfolio
3
- Version: 0.0.81
3
+ Version: 0.0.82
4
4
  Summary: Zero-Knowledge Proof framework for verifiable, private portfolio performance metrics
5
5
  Author-email: "Inference Labs, Inc." <info@inferencelabs.com>
6
6
  Requires-Python: >=3.10
@@ -1,2 +1,2 @@
1
1
  # This file is auto-generated during build
2
- __version__ = "0.0.81"
2
+ __version__ = "0.0.82"
@@ -6,37 +6,38 @@ import time
6
6
  import json
7
7
  import bittensor as bt
8
8
 
9
- # Constants for the circuit
10
9
  MAX_DAYS = 120
11
10
  MAX_SIGNALS = 256
12
11
  MERKLE_DEPTH = 8
13
- ARRAY_SIZE = 256
14
12
  SCALING_FACTOR = 10**7
13
+ RATIO_SCALE_FACTOR = 1_000_000
14
+ PRIME = 21888242871839275222246405745257275088548364400416034343698204186575808495617
15
15
 
16
16
 
17
- def run_command(command, cwd, verbose=True):
18
- """Executes a command in a given directory and returns the output."""
17
+ def log_verbose(verbose, level, message):
18
+ if verbose:
19
+ getattr(bt.logging, level)(message)
20
+
21
+
22
+ def get_attr(obj, attr):
23
+ """Get attribute from object or dictionary"""
24
+ return getattr(obj, attr) if hasattr(obj, attr) else obj[attr]
25
+
26
+
27
+ def run_command(command, cwd):
19
28
  result = subprocess.run(command, capture_output=True, text=True, cwd=cwd)
20
29
  if result.returncode != 0:
21
- if verbose:
22
- bt.logging.error("Error:")
23
- bt.logging.error(result.stdout)
24
- bt.logging.error(result.stderr)
30
+ bt.logging.error(f"Command failed: {' '.join(command)}")
31
+ bt.logging.error(f"stdout: {result.stdout}")
32
+ bt.logging.error(f"stderr: {result.stderr}")
25
33
  raise RuntimeError(
26
34
  f"Command {' '.join(command)} failed with exit code {result.returncode}"
27
35
  )
28
36
  return result.stdout
29
37
 
30
38
 
31
- def parse_nargo_struct_output(output):
32
- """
33
- Parses the raw output of a nargo execute command that returns a struct.
34
- """
35
- if (
36
- "[" in output
37
- and "]" in output
38
- and not ("MerkleTree" in output or "ReturnsData" in output)
39
- ):
39
+ def parse_circuit_output(output):
40
+ if "[" in output and "]" in output and "MerkleTree" not in output:
40
41
  array_matches = re.findall(r"\[([^\]]+)\]", output)
41
42
  if array_matches:
42
43
  array_content = array_matches[-1]
@@ -63,59 +64,37 @@ def parse_nargo_struct_output(output):
63
64
 
64
65
  if "MerkleTree" in output:
65
66
  tree = {}
66
- try:
67
- # Parse leaf_hashes
68
- if "leaf_hashes:" in struct_content:
69
- start = struct_content.find("leaf_hashes:") + len("leaf_hashes:")
70
- end = struct_content.find(", path_elements:")
71
- leaf_section = struct_content[start:end].strip()
72
- if leaf_section.startswith("[") and leaf_section.endswith("]"):
73
- leaf_content = leaf_section[1:-1]
74
- tree["leaf_hashes"] = [
75
- x.strip() for x in leaf_content.split(",") if x.strip()
76
- ]
77
-
78
- # Parse path_elements
79
- if "path_elements:" in struct_content:
80
- start = struct_content.find("path_elements:") + len("path_elements:")
81
- end = struct_content.find(", path_indices:")
82
- path_elem_section = struct_content[start:end].strip()
83
- tree["path_elements"] = parse_nested_arrays(path_elem_section)
84
-
85
- # Parse path_indices
86
- if "path_indices:" in struct_content:
87
- start = struct_content.find("path_indices:") + len("path_indices:")
88
- end = struct_content.find(", root:")
89
- path_idx_section = struct_content[start:end].strip()
90
- tree["path_indices"] = parse_nested_arrays(path_idx_section)
91
-
92
- # Parse root
93
- if "root:" in struct_content:
94
- start = struct_content.find("root:") + len("root:")
95
- root_section = struct_content[start:].strip().rstrip("}")
96
- tree["root"] = root_section.strip()
97
-
98
- return tree
99
- except Exception:
100
- pass
67
+ if "path_elements:" in struct_content:
68
+ start = struct_content.find("path_elements:") + len("path_elements:")
69
+ end = struct_content.find(", path_indices:")
70
+ path_elem_section = struct_content[start:end].strip()
71
+ tree["path_elements"] = parse_nested_arrays(path_elem_section)
101
72
 
102
- values = []
73
+ if "path_indices:" in struct_content:
74
+ start = struct_content.find("path_indices:") + len("path_indices:")
75
+ end = struct_content.find(", root:")
76
+ path_idx_section = struct_content[start:end].strip()
77
+ tree["path_indices"] = parse_nested_arrays(path_idx_section)
78
+
79
+ if "root:" in struct_content:
80
+ start = struct_content.find("root:") + len("root:")
81
+ root_section = struct_content[start:].strip().rstrip("}")
82
+ tree["root"] = root_section.strip()
83
+
84
+ return tree
103
85
 
86
+ values = []
104
87
  parts = re.split(r"[,\s]+", struct_content)
105
88
  for part in parts:
106
89
  part = part.strip("{}[](), \t\n\r")
107
90
  if not part:
108
91
  continue
109
-
110
- # Check if it's a hex value
111
92
  if part.startswith("0x") and len(part) > 2:
112
93
  try:
113
94
  values.append(str(int(part, 16)))
114
95
  continue
115
96
  except ValueError:
116
97
  pass
117
-
118
- # Check if it's a negative number
119
98
  if part.lstrip("-").isdigit():
120
99
  values.append(part)
121
100
 
@@ -123,7 +102,6 @@ def parse_nargo_struct_output(output):
123
102
 
124
103
 
125
104
  def parse_nested_arrays(section):
126
- """Helper function to parse nested array structures like [[...], [...]]"""
127
105
  if not section.strip().startswith("["):
128
106
  return []
129
107
 
@@ -134,116 +112,66 @@ def parse_nested_arrays(section):
134
112
  for char in section:
135
113
  if char == "[":
136
114
  depth += 1
137
- if depth == 2: # Start of inner array
115
+ if depth == 2:
138
116
  current_array = ""
139
- elif depth == 1: # Start of outer array
117
+ elif depth == 1:
140
118
  continue
141
119
  elif char == "]":
142
120
  depth -= 1
143
- if depth == 1: # End of inner array
121
+ if depth == 1:
144
122
  if current_array.strip():
145
123
  arrays.append(
146
124
  [x.strip() for x in current_array.split(",") if x.strip()]
147
125
  )
148
126
  current_array = ""
149
- elif depth == 0: # End of outer array
127
+ elif depth == 0:
150
128
  break
151
- elif depth == 2: # Inside inner array
129
+ elif depth == 2:
152
130
  current_array += char
153
131
 
154
132
  return arrays
155
133
 
156
134
 
157
- def parse_single_field_output(output):
158
- """
159
- Parses nargo output that contains a single field value.
160
- Handles both new hex format (0x...) and old Field() format.
161
- Returns the integer value, or None if no field found.
162
- """
163
- if "0x" in output:
164
- hex_match = output.split("0x")[1].split()[0]
165
- return int(hex_match, 16)
166
-
167
- if "Field(" in output:
168
- return int(output.split("Field(")[1].split(")")[0])
169
-
170
- return None
171
-
172
-
173
135
  def field_to_toml_value(f):
174
- """Converts a negative integer field to a proper field element string."""
175
- PRIME = (
176
- 21888242871839275222246405745257275088548364400416034343698204186575808495617
177
- )
178
- if f < 0:
179
- return str(f + PRIME)
180
- return str(f)
136
+ return str(f + PRIME) if f < 0 else str(f)
181
137
 
182
138
 
183
- def run_bb_prove(circuit_dir):
184
- """
185
- Runs barretenberg proving.
186
- Returns proof generation time and status.
187
- """
188
- bt.logging.info("\n--- Running Barretenberg Proof Generation ---")
139
+ def field_to_signed_int(field_str):
140
+ val = int(field_str, 16) if field_str.startswith("0x") else int(field_str)
141
+ return val - 2**64 if val >= 2**63 else val
189
142
 
143
+
144
+ def generate_bb_proof(circuit_dir):
190
145
  try:
191
146
  subprocess.run(["bb", "--version"], capture_output=True, check=True)
192
147
  except (subprocess.CalledProcessError, FileNotFoundError):
193
- bt.logging.info(
194
- "Error: bb (Barretenberg) not found. Please install it using \n`curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/cpp/installation/install | bash`"
148
+ bt.logging.error(
149
+ "bb (Barretenberg) not found. Install with: curl -L https://raw.githubusercontent.com/AztecProtocol/aztec-packages/master/barretenberg/cpp/installation/install | bash"
195
150
  )
196
151
  return None, False
197
152
 
198
- try:
199
- target_dir = os.path.join(circuit_dir, "target")
200
- proof_dir = os.path.join(circuit_dir, "proof")
201
- vk_dir = os.path.join(circuit_dir, "vk")
202
-
203
- os.makedirs(proof_dir, exist_ok=True)
204
- os.makedirs(vk_dir, exist_ok=True)
205
-
206
- witness_file = os.path.join(target_dir, "witness.gz")
207
- circuit_file = os.path.join(target_dir, "circuits.json")
208
-
209
- proof_file = proof_dir
210
-
211
- prove_start = time.time()
212
- prove_result = subprocess.run(
213
- ["bb", "prove", "-b", circuit_file, "-w", witness_file, "-o", proof_file],
214
- capture_output=True,
215
- text=True,
216
- cwd=circuit_dir,
217
- )
218
- prove_time = time.time() - prove_start
153
+ target_dir = os.path.join(circuit_dir, "target")
154
+ proof_dir = os.path.join(circuit_dir, "proof")
155
+ os.makedirs(proof_dir, exist_ok=True)
219
156
 
220
- if prove_result.returncode != 0:
221
- bt.logging.info("bb prove failed:")
222
- bt.logging.info(
223
- " ".join(
224
- [
225
- "bb",
226
- "prove",
227
- "-b",
228
- circuit_file,
229
- "-w",
230
- witness_file,
231
- "-o",
232
- proof_file,
233
- ]
234
- )
235
- )
236
- bt.logging.info(prove_result.stdout)
237
- bt.logging.info(prove_result.stderr)
238
- return None, False
157
+ witness_file = os.path.join(target_dir, "witness.gz")
158
+ circuit_file = os.path.join(target_dir, "circuits.json")
239
159
 
240
- bt.logging.info(f"Proof generated in {prove_time:.3f}s")
241
- return prove_time, True
160
+ prove_start = time.time()
161
+ prove_result = subprocess.run(
162
+ ["bb", "prove", "-b", circuit_file, "-w", witness_file, "-o", proof_dir],
163
+ capture_output=True,
164
+ text=True,
165
+ cwd=circuit_dir,
166
+ )
167
+ prove_time = time.time() - prove_start
242
168
 
243
- except Exception as e:
244
- bt.logging.error(f"Error during proof generation/verification: {e}")
169
+ if prove_result.returncode != 0:
170
+ bt.logging.error(f"bb prove failed: {prove_result.stderr}")
245
171
  return None, False
246
172
 
173
+ return prove_time, True
174
+
247
175
 
248
176
  def generate_proof(
249
177
  data=None,
@@ -256,43 +184,26 @@ def generate_proof(
256
184
  witness_only=False,
257
185
  account_size=None,
258
186
  ):
259
- """
260
- Core proof generation logic.
261
-
262
- Args:
263
- data: Optional dictionary containing perf_ledgers and positions.
264
- If None, will read from validator_checkpoint.json
265
- miner_hotkey: The hotkey of the miner to generate proof for.
266
- If None and reading from file, uses first available hotkey
267
- verbose: Optional boolean to control logging verbosity.
268
- If None, auto-detects (demo mode = verbose, production = minimal)
269
- annual_risk_free_percentage: Annual risk-free rate percentage (default 4.19)
270
- use_weighting: Whether to use weighted calculations (default False)
271
- bypass_confidence: Whether to bypass confidence thresholds (default False)
272
- daily_checkpoints: Number of checkpoints expected per day (default 2)
273
- witness_only: If True, skip barretenberg proof generation for faster testing (default False)
274
-
275
- Returns:
276
- Dictionary with proof results including status, portfolio_metrics, etc.
277
- """
278
- bt.logging.info(
279
- f"generate_proof called with verbose={verbose}, miner_hotkey={miner_hotkey[:8] if miner_hotkey else None}"
280
- )
281
-
282
- # Auto-detect mode: demo mode if reading from file, production if data provided
283
187
  is_demo_mode = data is None
284
188
  if verbose is None:
285
189
  verbose = is_demo_mode
286
190
 
287
- bt.logging.info(
288
- f"After auto-detect: verbose={verbose}, is_demo_mode={is_demo_mode}"
191
+ log_verbose(
192
+ verbose,
193
+ "info",
194
+ f"generate_proof called with miner_hotkey={miner_hotkey[:8] if miner_hotkey else None}",
195
+ )
196
+ log_verbose(
197
+ verbose,
198
+ "info",
199
+ f"Mode: {'Demo' if is_demo_mode else 'Production'}, verbose={verbose}",
289
200
  )
290
201
 
291
202
  try:
292
203
  if data is None:
293
- if verbose:
294
- bt.logging.info("Loading data from validator_checkpoint.json...")
295
-
204
+ log_verbose(
205
+ verbose, "info", "Loading data from validator_checkpoint.json..."
206
+ )
296
207
  with open("validator_checkpoint.json", "r") as f:
297
208
  data = json.load(f)
298
209
  except Exception as e:
@@ -300,13 +211,13 @@ def generate_proof(
300
211
 
301
212
  if miner_hotkey is None:
302
213
  miner_hotkey = list(data["perf_ledgers"].keys())[0]
303
- if verbose:
304
- bt.logging.info(
305
- f"No hotkey specified, using first available: {miner_hotkey}"
306
- )
214
+ log_verbose(
215
+ verbose,
216
+ "info",
217
+ f"No hotkey specified, using first available: {miner_hotkey}",
218
+ )
307
219
  else:
308
- if verbose:
309
- bt.logging.info(f"Using specified hotkey: {miner_hotkey}")
220
+ log_verbose(verbose, "info", f"Using specified hotkey: {miner_hotkey}")
310
221
 
311
222
  if miner_hotkey not in data["perf_ledgers"]:
312
223
  raise ValueError(
@@ -314,18 +225,18 @@ def generate_proof(
314
225
  )
315
226
 
316
227
  positions = data["positions"][miner_hotkey]["positions"]
317
- if verbose:
318
- bt.logging.info("Preparing circuit inputs...")
228
+ log_verbose(verbose, "info", "Preparing circuit inputs...")
319
229
 
320
230
  # Use daily returns calculated by PTN
321
231
  daily_log_returns = data.get("daily_returns", [])
322
232
  n_returns = len(daily_log_returns)
323
233
 
324
234
  if n_returns > MAX_DAYS:
325
- if verbose:
326
- bt.logging.warning(
327
- f"Warning: Miner has {n_returns} daily returns, but circuit only supports {MAX_DAYS}. Truncating."
328
- )
235
+ log_verbose(
236
+ verbose,
237
+ "warning",
238
+ f"Truncating {n_returns} daily returns to {MAX_DAYS} (circuit limit)",
239
+ )
329
240
  daily_log_returns = daily_log_returns[:MAX_DAYS]
330
241
  n_returns = MAX_DAYS
331
242
 
@@ -335,19 +246,19 @@ def generate_proof(
335
246
  # Pad to MAX_DAYS
336
247
  scaled_log_returns += [0] * (MAX_DAYS - len(scaled_log_returns))
337
248
 
338
- if verbose:
339
- bt.logging.info(f"Using {n_returns} daily returns from PTN")
249
+ log_verbose(verbose, "info", f"Using {n_returns} daily returns from PTN")
340
250
 
341
251
  all_orders = []
342
252
  for pos in positions:
343
- all_orders.extend(pos["orders"])
253
+ all_orders.extend(get_attr(pos, "orders"))
344
254
 
345
255
  signals_count = len(all_orders)
346
256
  if signals_count > MAX_SIGNALS:
347
- if verbose:
348
- bt.logging.warning(
349
- f"Warning: Miner has {signals_count} signals, but circuit only supports {MAX_SIGNALS}. Truncating."
350
- )
257
+ log_verbose(
258
+ verbose,
259
+ "warning",
260
+ f"Truncating {signals_count} signals to {MAX_SIGNALS} (circuit limit)",
261
+ )
351
262
  all_orders = all_orders[:MAX_SIGNALS]
352
263
  signals_count = MAX_SIGNALS
353
264
 
@@ -356,24 +267,34 @@ def generate_proof(
356
267
 
357
268
  signals = []
358
269
  for order in all_orders:
359
- trade_pair_str = order.get("trade_pair", ["UNKNOWN"])[0]
270
+ trade_pair = get_attr(order, "trade_pair")
271
+ trade_pair_str = (
272
+ str(trade_pair).split(".")[1]
273
+ if hasattr(trade_pair, "name")
274
+ else str(trade_pair)
275
+ )
360
276
  if trade_pair_str not in trade_pair_map:
361
277
  trade_pair_map[trade_pair_str] = trade_pair_counter
362
278
  trade_pair_counter += 1
363
279
 
364
- order_type_str = order["order_type"]
280
+ order_type = get_attr(order, "order_type")
281
+ order_type_str = (
282
+ str(order_type).split(".")[1]
283
+ if hasattr(order_type, "name")
284
+ else str(order_type)
285
+ )
365
286
  order_type_map = {"SHORT": 2, "LONG": 1, "FLAT": 0}
366
- price = int(order.get("price", 0) * SCALING_FACTOR)
367
- order_uuid = order.get("order_uuid", "0")
368
- bid = int(order.get("bid", 0) * SCALING_FACTOR)
369
- ask = int(order.get("ask", 0) * SCALING_FACTOR)
370
- processed_ms = order.get("processed_ms", 0)
287
+ price = int(get_attr(order, "price") * SCALING_FACTOR)
288
+ order_uuid = get_attr(order, "order_uuid")
289
+ bid = int(get_attr(order, "bid") * SCALING_FACTOR)
290
+ ask = int(get_attr(order, "ask") * SCALING_FACTOR)
291
+ processed_ms = get_attr(order, "processed_ms")
371
292
 
372
293
  signals.append(
373
294
  {
374
295
  "trade_pair": str(trade_pair_map[trade_pair_str]),
375
296
  "order_type": str(order_type_map.get(order_type_str, 0)),
376
- "leverage": str(int(abs(order.get("leverage", 0)) * SCALING_FACTOR)),
297
+ "leverage": str(int(abs(order.leverage) * SCALING_FACTOR)),
377
298
  "price": str(price),
378
299
  "processed_ms": str(processed_ms),
379
300
  "order_uuid": f"0x{order_uuid.replace('-', '')}",
@@ -396,39 +317,26 @@ def generate_proof(
396
317
  }
397
318
  ] * (MAX_SIGNALS - len(signals))
398
319
 
399
- if verbose:
400
- bt.logging.info(
401
- f"Prepared {n_returns} daily returns and {signals_count} signals for circuit."
402
- )
320
+ log_verbose(
321
+ verbose,
322
+ "info",
323
+ f"Prepared {n_returns} daily returns and {signals_count} signals for circuit",
324
+ )
403
325
 
326
+ if verbose:
404
327
  bt.logging.info(f"Circuit daily returns count: {n_returns}")
405
- bt.logging.info("Circuit First 10 daily returns:")
406
- for i in range(min(10, n_returns)):
328
+ bt.logging.info("Sample daily returns:")
329
+ for i in range(min(5, n_returns)):
407
330
  bt.logging.info(
408
331
  f" [{i}] return={daily_log_returns[i]:.6f} (scaled={scaled_log_returns[i]})"
409
332
  )
410
-
411
- if n_returns > 10:
412
- bt.logging.info("Circuit Last 5 daily returns:")
413
- for i in range(n_returns - 5, n_returns):
414
- if i >= 0:
415
- bt.logging.info(
416
- f" [{i}] return={daily_log_returns[i]:.6f} (scaled={scaled_log_returns[i]})"
417
- )
418
-
419
333
  if daily_log_returns:
420
334
  mean_return = sum(daily_log_returns) / len(daily_log_returns)
421
- bt.logging.info(
422
- f"Circuit daily returns stats: mean={mean_return:.6f}, count={n_returns}"
423
- )
424
-
425
- bt.logging.info(f"Daily returns from PTN: {n_returns}")
335
+ bt.logging.info(f"Mean daily return: {mean_return:.6f}, count={n_returns}")
426
336
  bt.logging.info(f"Circuit Config: MAX_DAYS={MAX_DAYS}, DAILY_CHECKPOINTS=2")
427
337
 
428
- if verbose:
429
- bt.logging.info("Running tree_generator circuit...")
430
- else:
431
- bt.logging.info(f"Generating tree for hotkey {miner_hotkey}...")
338
+ log_verbose(verbose, "info", "Running tree_generator circuit...")
339
+ bt.logging.info(f"Generating tree for hotkey {miner_hotkey[:8]}...")
432
340
  current_dir = os.path.dirname(os.path.abspath(__file__))
433
341
  tree_generator_dir = os.path.join(current_dir, "tree_generator")
434
342
 
@@ -438,12 +346,11 @@ def generate_proof(
438
346
  toml.dump(tree_prover_input, f)
439
347
 
440
348
  output = run_command(
441
- ["nargo", "execute", "--silence-warnings" if not verbose else ""],
349
+ ["nargo", "execute", "--silence-warnings"],
442
350
  tree_generator_dir,
443
- verbose,
444
351
  )
445
352
 
446
- tree = parse_nargo_struct_output(output)
353
+ tree = parse_circuit_output(output)
447
354
  try:
448
355
  path_elements = tree["path_elements"]
449
356
  path_indices = tree["path_indices"]
@@ -453,23 +360,15 @@ def generate_proof(
453
360
  "Unexpected tree_generator output structure, expected MerkleTree dict with leaf_hashes, path_elements, path_indices, and root"
454
361
  )
455
362
 
456
- if verbose:
457
- bt.logging.info(f"Generated signals Merkle root: {signals_merkle_root}")
458
- if isinstance(signals_merkle_root, str) and signals_merkle_root.startswith(
459
- "0x"
460
- ):
461
- bt.logging.info(f"Signals Merkle root (hex): {signals_merkle_root}")
462
- else:
463
- bt.logging.info(f"Signals Merkle root (int): {signals_merkle_root}")
464
-
465
- if verbose:
466
- bt.logging.info("Returns Merkle root will be calculated within the circuit")
467
- bt.logging.info(f"Number of daily returns: {n_returns}")
468
-
469
- if verbose:
470
- bt.logging.info("Running main proof of portfolio circuit...")
471
- else:
472
- bt.logging.info(f"Generating witness for hotkey {miner_hotkey}...")
363
+ log_verbose(
364
+ verbose, "info", f"Generated signals Merkle root: {signals_merkle_root}"
365
+ )
366
+ log_verbose(
367
+ verbose, "info", "Returns Merkle root will be calculated within circuit"
368
+ )
369
+ log_verbose(verbose, "info", f"Number of daily returns: {n_returns}")
370
+ log_verbose(verbose, "info", "Running main proof of portfolio circuit...")
371
+ bt.logging.info(f"Generating witness for hotkey {miner_hotkey[:8]}...")
473
372
  main_circuit_dir = os.path.join(current_dir, "circuits")
474
373
 
475
374
  # Pass annual risk-free rate (to match ann_excess_return usage)
@@ -513,17 +412,15 @@ def generate_proof(
513
412
  with open(os.path.join(main_circuit_dir, "Prover.toml"), "w") as f:
514
413
  toml.dump(main_prover_input, f)
515
414
 
516
- if verbose:
517
- bt.logging.info("Executing main circuit to generate witness...")
415
+ log_verbose(verbose, "info", "Executing main circuit to generate witness...")
518
416
  witness_start = time.time()
519
417
  output = run_command(
520
- ["nargo", "execute", "witness", "--silence-warnings"], main_circuit_dir, verbose
418
+ ["nargo", "execute", "witness", "--silence-warnings"], main_circuit_dir
521
419
  )
522
420
  witness_time = time.time() - witness_start
523
- if verbose:
524
- bt.logging.info(f"Witness generation completed in {witness_time:.3f}s")
421
+ log_verbose(verbose, "info", f"Witness generation completed in {witness_time:.3f}s")
525
422
 
526
- fields = parse_nargo_struct_output(output)
423
+ fields = parse_circuit_output(output)
527
424
  if len(fields) < 9:
528
425
  raise RuntimeError(
529
426
  f"Expected 9 output fields from main circuit, got {len(fields)}: {fields}"
@@ -585,13 +482,14 @@ def generate_proof(
585
482
 
586
483
  if witness_only:
587
484
  prove_time, proving_success = None, True
588
- if verbose:
589
- bt.logging.info(
590
- "Skipping barretenberg proof generation (witness_only=True)"
591
- )
485
+ log_verbose(
486
+ verbose,
487
+ "info",
488
+ "Skipping barretenberg proof generation (witness_only=True)",
489
+ )
592
490
  else:
593
491
  try:
594
- prove_time, proving_success = run_bb_prove(main_circuit_dir)
492
+ prove_time, proving_success = generate_bb_proof(main_circuit_dir)
595
493
  if prove_time is None:
596
494
  bt.logging.error("Barretenberg proof generation failed")
597
495
  prove_time, proving_success = None, False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proof-of-portfolio
3
- Version: 0.0.81
3
+ Version: 0.0.82
4
4
  Summary: Zero-Knowledge Proof framework for verifiable, private portfolio performance metrics
5
5
  Author-email: "Inference Labs, Inc." <info@inferencelabs.com>
6
6
  Requires-Python: >=3.10
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "proof-of-portfolio"
8
- version = "0.0.81"
8
+ version = "0.0.82"
9
9
  description = "Zero-Knowledge Proof framework for verifiable, private portfolio performance metrics"
10
10
  readme = "README.md"
11
11
  authors = [{ name = "Inference Labs, Inc.", email = "info@inferencelabs.com" }]