Bläddra i källkod

The public sigma-rs artifact

Ian Goldberg 2 månader sedan
incheckning
ca338c4255

+ 88 - 0
README.md

@@ -0,0 +1,88 @@
+# Code artifact for `sigma-rs`
+
+This repository contains the code artifact for `sigma-rs`, a Rust
+software stack for implementing protocols based on keyed-verification
+anonymous credentials (KVAC).
+
+## Artifact structure
+
+The directories in this repository are as follows:
+
+  - [`Scripts`](Scripts/)
+    : Useful scripts for building a docker image for this artifact, and running tests therein
+  - [`application-lox-zkp`](application-lox-zkp/)
+    : A fork of [the Tor Project's Lox code repository](https://gitlab.torproject.org/tpo/anti-censorship/lox), which uses de Valence's [zkp crate](https://github.com/dalek-cryptography/zkp) to implement its zero-knowledge proofs; we added code to perform benchmarks to compare the zkp-based implementation to our own.  The main subdirectory containing the zkp-based Lox implementation is [`application-lox-zkp/crates/lox-library`](application-lox-zkp/crates/lox-library/).
+  - [`application-lox`](application-lox/)
+    : Another fork of [the Tor Project's Lox code repository](https://gitlab.torproject.org/tpo/anti-censorship/lox), which uses our `sigma-rs` software stack to re-implement the Lox protocols.  Our re-implementation is in the [`application-lox/crates/lox-extensions`](application-lox/crates/lox-extensions/) subdirectory.
+  - [`application-ooni`](application-ooni/)
+    : Our implementation of anonymous credential protocols for [OONI](https://ooni.org/), the Open Observatory for Network Interference.  The main implementation is in the [`application-ooni/ooniauth-core`](application-ooni/ooniauth-core/) subdirectory.
+  - [`cmz`](cmz/), [`sigma-compiler`](sigma-compiler/), [`sigma-proofs`](sigma-proofs/), [`spongefish`](spongefish/)
+    : The constituent crates in our `sigma-rs` software stack
+
+## Building the artifact
+
+You will need `docker` installed on your host system.
+
+After downloading or cloning this repository, build a docker image with:
+```bash
+  ./Scripts/build-docker
+```
+
+On a recentish laptop, this image should take around 10 minutes to build.  The resulting image is about 8 GB.
+
+## Start the docker
+
+Start the docker with:
+
+```bash
+  docker run -it sigma-rs bash
+```
+
+**All of the remaining commands below should be run _within_ the docker.**
+
+## Running the unit tests
+
+To ensure everything has built properly, you should run the unit tests within the docker with:
+
+```bash
+  Scripts/run_all_tests
+```
+
+This should take less than 10 seconds to run.
+
+## Running the Lox native client / native server benchmarks
+
+To run the Lox native client / native server benchmarks (the "native" columns in Table 2 of the paper):
+
+  - zkp version: `(cd application-lox-zkp/crates/lox-library/; ./run_test.sh)`  
+    The results will be placed in the `application-lox-zkp/crates/lox-library/parsed_results/` directory
+  - `sigma-rs` version: `(cd application-lox/crates/lox-extensions/; ./run_test.sh)`  
+    The results will be placed in the `application-lox/crates/lox-extensions/parsed_results/` directory
+
+Each run should take less than 30 seconds.
+
+## Running the Lox wasm client / native server benchmarks
+
+To run the Lox wasm client / native server benchmarks (the "wasm" columns in Table 2), 
+follow the instructions in [`application-lox/crates/lox-extensions/TESTING.md`](application-lox/crates/lox-extensions/TESTING.md).  For now, you will have to manually adapt the instructions to a docker environment (more automation coming soon!).
+
+## Running the OONI native benchmarks
+
+To run the OONI native benchmarks (the "native" columns in Table 3):
+
+```bash
+(cd application-ooni && \
+    cargo bench -p ooniauth-core && \
+    python3 scripts/criterion_extract.py )
+```
+
+This benchmark should take under 2 minutes to run.
+
+## Running the OONI iOS benchmarks
+
+To run the OONI iOS benchmarks (the "iOS" column in Table 3), see the instructions in [`application-ooni/ios/README.md`](application-ooni/ios/README.md).
+
+## Using `sigma-rs` in your own code
+
+For instructions on using the `sigma-rs` stack to implement your own
+KVAC protocols, see [`cmz/README.md`](cmz/README.md).

+ 62 - 0
Scripts/Dockerfile

@@ -0,0 +1,62 @@
+FROM ubuntu:24.04
+RUN apt update && apt install -y build-essential rustup python3-pip libssl-dev pkg-config git curl make iproute2
+
+# rdsys (one of the dependencies for the Lox benchmarks) is written in Go
+RUN curl -OL https://golang.org/dl/go1.25.6.linux-amd64.tar.gz \
+    && tar -C /usr/local -xzf go1.25.6.linux-amd64.tar.gz \
+    && rm go1.25.6.linux-amd64.tar.gz
+
+# Set up Go environment
+ENV PATH=$PATH:/usr/local/go/bin
+
+# Verify installation
+RUN go version
+
+# Install a recent Rust compiler
+RUN install -D /usr/bin/rustup /root/.cargo/bin/rustup && /root/.cargo/bin/rustup install 1.92.0
+
+COPY . /root/sigma-rs-artifact/
+
+# Build all the tests, binaries, benchmarks
+WORKDIR /root/sigma-rs-artifact/spongefish
+RUN cargo build --release --all-targets
+RUN cargo build --release --all-targets -p spongefish
+WORKDIR /root/sigma-rs-artifact/sigma-proofs
+RUN cargo build --release --all-targets
+WORKDIR /root/sigma-rs-artifact/sigma-compiler
+RUN cargo build --release --all-targets
+WORKDIR /root/sigma-rs-artifact/sigma-compiler/sigma-compiler-core
+RUN cargo build --release --all-targets
+WORKDIR /root/sigma-rs-artifact/sigma-compiler/sigma-compiler-derive
+RUN cargo build --release --all-targets
+WORKDIR /root/sigma-rs-artifact/cmz
+RUN cargo build --release --all-targets
+WORKDIR /root/sigma-rs-artifact/cmz/cmz-core
+RUN cargo build --release --all-targets
+WORKDIR /root/sigma-rs-artifact/cmz/cmz-derive
+RUN cargo build --release --all-targets
+WORKDIR /root/sigma-rs-artifact/application-ooni
+RUN cargo build --release --all-targets
+RUN cargo build --release --all-targets -p ooniauth-core
+WORKDIR /root/sigma-rs-artifact/application-lox/crates/lox-extensions
+RUN cargo build --release --features=bridgeauth,test --all-targets
+WORKDIR /root/sigma-rs-artifact/application-lox/crates/lox-distributor
+RUN cargo build --release --features=test-branch --all-targets
+WORKDIR /root/sigma-rs-artifact/application-lox-zkp/crates/lox-library
+RUN cargo build --release --features=bridgeauth --all-targets
+WORKDIR /root/sigma-rs-artifact/application-lox-zkp/crates/lox-distributor
+RUN cargo build --release --features=test-branch --all-targets
+
+# Build the wasm testing environment
+WORKDIR /root/sigma-rs-artifact
+RUN git clone https://gitlab.torproject.org/tpo/anti-censorship/rdsys.git
+WORKDIR /root/sigma-rs-artifact/rdsys
+RUN make build
+RUN make descriptors
+WORKDIR /root/sigma-rs-artifact/application-lox/crates/lox-wasm
+RUN cargo install wasm-pack
+RUN /root/.cargo/bin/wasm-pack build --release --target web
+WORKDIR /root/sigma-rs-artifact/application-lox-zkp/crates/lox-wasm
+RUN /root/.cargo/bin/wasm-pack build --release --target web
+
+WORKDIR /root/sigma-rs-artifact/

+ 11 - 0
Scripts/build-docker

@@ -0,0 +1,11 @@
+#!/bin/bash
+
+# cd into the directory containing this script (from the bash faq 028)
+if [[ $BASH_SOURCE = */* ]]; then
+  cd -- "${BASH_SOURCE%/*}/" || exit
+fi
+
+./clone-repos || exit 1
+
+cd ..
+docker build $* -t ${SIGMA_RS_DOCKER_PREFIX}sigma-rs -f Scripts/Dockerfile .

+ 37 - 0
Scripts/clone-repos

@@ -0,0 +1,37 @@
+#!/bin/bash
+
+# cd into the directory containing this script (from the bash faq 028)
+if [[ $BASH_SOURCE = */* ]]; then
+  cd -- "${BASH_SOURCE%/*}/" || exit
+fi
+
+cd ..
+
+fetch_repo() {
+    repo_url="$1"
+    commit_id="$2"
+    dir_name="$3"
+
+    if [ -d "$dir_name" ]; then
+        ( cd "$dir_name" && git fetch origin --tags && \
+            git checkout "$commit_id" )
+    else
+        git clone "$repo_url" "$dir_name" && \
+        ( cd "$dir_name" && git checkout "$commit_id" && \
+            if [ -e "../patches/${dir_name}.diff" ]; then
+                patch -p1 < ../patches/${dir_name}.diff || exit 1
+            fi )
+    fi
+}
+
+fetch_repos() {
+    fetch_repo https://github.com/arkworks-rs/spongefish v0.5.1 spongefish && \
+    fetch_repo https://github.com/mmaker/sigma-rs v0.3.0 sigma-proofs && \
+    fetch_repo https://git-crysp.uwaterloo.ca/SigmaProtocol/sigma-compiler 0.2.2 sigma-compiler && \
+    fetch_repo https://git-crysp.uwaterloo.ca/SigmaProtocol/cmz 0.2.1 cmz && \
+    fetch_repo https://gitlab.torproject.org/onyinyang/lox.git/ lox-artifact application-lox && \
+    fetch_repo https://gitlab.torproject.org/onyinyang/lox.git/ lox-artifact-zkp application-lox-zkp && \
+    fetch_repo https://github.com/ooni/userauth artifact-v0.2 application-ooni
+}
+
+fetch_repos

+ 11 - 0
Scripts/remove-repos

@@ -0,0 +1,11 @@
+#!/bin/bash
+
+# cd into the directory containing this script (from the bash faq 028)
+if [[ $BASH_SOURCE = */* ]]; then
+  cd -- "${BASH_SOURCE%/*}/" || exit
+fi
+
+cd ..
+
+rm -rf spongefish sigma-proofs sigma-compiler cmz application-lox \
+    application-lox-zkp application-ooni

+ 23 - 0
Scripts/run_all_tests

@@ -0,0 +1,23 @@
+#!/bin/bash
+
+# cd into the directory containing this script (from the bash faq 028)
+if [[ $BASH_SOURCE = */* ]]; then
+  cd -- "${BASH_SOURCE%/*}/" || exit
+fi
+
+# All of the crates and workspaces
+CRATES="spongefish spongefish/spongefish sigma-proofs
+    sigma-compiler sigma-compiler/sigma-compiler-derive
+    sigma-compiler/sigma-compiler-core cmz cmz/cmz-derive cmz/cmz-core
+    application-ooni"
+
+cd ..
+for c in $CRATES; do
+    (cd $c && cargo test --release) || exit 1
+done
+
+# The Lox tests need extra feature flags
+(cd application-lox/crates/lox-distributor && cargo test --release --features=test-branch) || exit 1
+(cd application-lox-zkp/crates/lox-distributor && cargo test --release --features=test-branch) || exit 1
+(cd application-lox/crates/lox-extensions && cargo test --release --features=bridgeauth,test) || exit 1
+(cd application-lox-zkp/crates/lox-library && cargo test --release --features=bridgeauth) || exit 1

+ 116 - 0
Scripts/run_lox_benches

@@ -0,0 +1,116 @@
+#!/usr/bin/env python3
+
+import os
+import re
+import subprocess
+import sys
+
+def run_bench(cmd):
+    """Run the benchmark with the given command (passed as an array of
+    arguments).  The benchmark is expected to output on stdout one or more
+    csv tables, of the form:
+
+    === Table 5 ===
+
+    protocol,request size,response size
+    Open Invitation,249,543
+    Trust Promotion,1677,2285
+    Trust Migration,845,241
+    Level Up,2253,241
+    Issue Invite,1101,401
+    Redeem Invite,1037,241
+    Check Blockage,685,325
+    Blockage Migration,973,241
+    Update Invite,557,209
+    Update Credential,749,209
+    
+    This function will return a dictionary with keys being the name of the
+    table ("Table 5"), and the value being a list of csv rows, where each row
+    is a dict with keys being the name of the column, and the value being the
+    entry in that column."""
+    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True)
+    table_name = None
+    seen_nonempty_line = False
+    retval = {}
+    table = []
+    headers = []
+    for line in proc.stdout:
+        print(line,end='')
+        line = line.rstrip()
+        if matched := re.match(r'=== (.*) ===', line):
+            if table_name is not None:
+                # Store the previous table
+                retval[table_name] = table
+            table_name = matched.group(1)
+            seen_nonempty_line = False
+            table = []
+            headers = []
+            continue
+        if line == "" and seen_nonempty_line == True:
+            if table_name is not None:
+                # The table is finished; store it
+                retval[table_name] = table
+                table_name = None
+                seen_nonempty_line = False
+                table = []
+                headers = []
+            continue
+        if line == "":
+            continue
+        # At this point, we know the line is nonempty
+        if len(headers) == 0:
+            # This should be the header line
+            headers = line.split(',')
+            seen_nonempty_line = True
+        else:
+            values = line.split(',')
+            row = dict(zip(headers, values))
+            table.append(row)
+
+    # If stdout ends, and there's a table in progress, store it
+    if table_name is not None:
+        retval[table_name] = table
+
+    return retval
+
+os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
+os.environ['PYTHONUNBUFFERED'] = '1'
+if len(sys.argv) > 1:
+    niters = sys.argv[1]
+else:
+    niters = "10"
+
+lox_sigma_native = run_bench(["./run_native_bench", niters])
+lox_zkp_native = run_bench(["./run_native_bench", "-z", niters])
+lox_sigma_wasm = run_bench(["./run_wasm_bench", niters])
+lox_zkp_wasm = run_bench(["./run_wasm_bench", "-z", niters])
+
+print("\n*** Complete Table 2 ***\n")
+print("protocol,zkp client native ms,zkp client wasm ms,zkp server native ms,sigma-rs client native ms,sigma-rs client wasm ms,sigma-rs server native ms")
+for r in range(len(lox_sigma_native['Table 2'])):
+    proto = lox_sigma_native['Table 2'][r]['protocol']
+    assert proto == lox_zkp_native['Table 2'][r]['protocol']
+    assert proto == lox_sigma_wasm['Table 2'][r]['protocol']
+    assert proto == lox_zkp_wasm['Table 2'][r]['protocol']
+    print(','.join([
+        proto,
+        lox_zkp_native['Table 2'][r]['client native ms'],
+        lox_zkp_wasm['Table 2'][r]['client wasm ms'],
+        lox_zkp_native['Table 2'][r]['server native ms'],
+        lox_sigma_native['Table 2'][r]['client native ms'],
+        lox_sigma_wasm['Table 2'][r]['client wasm ms'],
+        lox_sigma_native['Table 2'][r]['server native ms'],
+    ]))
+
+print("\n*** Complete Table 5 ***\n")
+print("protocol,zkp request bytes,zkp response bytes,sigma-rs request bytes,sigma-rs response bytes")
+for r in range(len(lox_sigma_native['Table 5'])):
+    proto = lox_sigma_native['Table 5'][r]['protocol']
+    assert proto == lox_zkp_native['Table 5'][r]['protocol']
+    print(','.join([
+        proto,
+        lox_zkp_native['Table 5'][r]['request size'],
+        lox_zkp_native['Table 5'][r]['response size'],
+        lox_sigma_native['Table 5'][r]['request size'],
+        lox_sigma_native['Table 5'][r]['response size'],
+    ]))

+ 87 - 0
Scripts/run_native_bench

@@ -0,0 +1,87 @@
+#!/usr/bin/env python3
+
+# Usage: ./run_native_bench [-z] [niters]
+# Use -z to benchmark the legacy zkp code; otherwise benchmark our sigma-rs code
+# niters defaults to 10
+
+import os
+import re
+import subprocess
+import sys
+
+progname = sys.argv.pop(0)
+if len(sys.argv) > 0 and sys.argv[0] == "-z":
+    sys.argv.pop(0)
+    loxdir = "../application-lox-zkp/crates/lox-library"
+    cargo_features = "--features=bridgeauth"
+else:
+    loxdir = "../application-lox/crates/lox-extensions"
+    cargo_features = "--features=bridgeauth,test"
+
+if len(sys.argv) > 0:
+    niters = sys.argv[0]
+else:
+    niters = "10"
+
+os.environ['LOX_BENCH_NITERS'] = niters
+os.chdir(os.path.dirname(os.path.realpath(progname)))
+os.chdir(loxdir)
+
+proc = subprocess.Popen(["cargo", "test", "--release", "bench_",
+    cargo_features, "--", "--nocapture", "--test-threads", "1"],
+    stdout=subprocess.PIPE, text=True)
+protocol = None
+protocol_map = {
+    'BLOCKAGE-MIGRATION': 'Blockage Migration',
+    'CHECK-BLOCKAGE': 'Check Blockage',
+    'ISSUE-INVITATION': 'Issue Invite',
+    'LEVEL-UP-2: 44 days': 'Level Up',
+    'TRUST-MIGRATION-0: 30 days': 'Trust Migration',
+    'OPEN-INVITATION': 'Open Invitation',
+    'REDEEM-INVITATION': 'Redeem Invite',
+    'TRUST-PROMOTION-1: 30 days': 'Trust Promotion',
+    'UPDATE-CRED': 'Update Credential',
+    'UPDATE-INVITE': 'Update Invite',
+}
+req_size = {}
+resp_size = {}
+client_time = {}
+server_time = {}
+for line in proc.stdout:
+    print(line,end='')
+    if matched := re.match(r'----(.*)----', line):
+        protocol = protocol_map[matched.group(1)]
+        continue
+    if matched := re.match(r'Request bytes range: \[\d+, (\d+)\]', line):
+        req_size[protocol] = matched.group(1)
+    elif matched := re.match(r'Response bytes range: \[\d+, (\d+)\]', line):
+        resp_size[protocol] = matched.group(1)
+    elif matched := re.match(r'Total client ms: ([\.\d]+) ± ([\.\d]+)', line):
+        client_time[protocol] = (matched.group(1), matched.group(2))
+    elif matched := re.match(r'Response ms: ([\.\d]+) ± ([\.\d]+)', line):
+        server_time[protocol] = (matched.group(1), matched.group(2))
+
+# The order in which we output the protocols
+protocol_list = [
+    'Open Invitation',
+    'Trust Promotion',
+    'Trust Migration',
+    'Level Up',
+    'Issue Invite',
+    'Redeem Invite',
+    'Check Blockage',
+    'Blockage Migration',
+    'Update Invite',
+    'Update Credential',
+]
+
+print("\n=== Table 2 ===\n")
+print("protocol,client native ms,server native ms")
+for p in protocol_list:
+    print(f"{p},{client_time[p][0]} ({client_time[p][1]}),{server_time[p][0]} ({server_time[p][1]})")
+
+print("\n=== Table 5 ===\n")
+print("protocol,request size,response size")
+for p in protocol_list:
+    print(f"{p},{req_size[p]},{resp_size[p]}")
+print()

+ 26 - 0
Scripts/run_ooni_benches

@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+
+import os
+import re
+import subprocess
+import sys
+
+os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
+os.chdir("../application-ooni")
+os.environ['PYTHONUNBUFFERED'] = '1'
+
+subprocess.run(["cargo", "bench", "-p", "ooniauth-core"])
+proc = subprocess.Popen(["python3", "scripts/criterion_extract.py"], stdout=subprocess.PIPE, text=True)
+timings = {}
+for line in proc.stdout:
+    line = line.rstrip()
+    if matches := re.match(r'(client|server)_(reg|submit|update)\s+([\d\.]+)\s+([\d\.]+)', line):
+        timings[f"{matches.group(1)}_{matches.group(2)}"] = f"{matches.group(3)} ({matches.group(4)})"
+    else:
+        print(f"Unparsable line: {line}")
+
+print("\n=== Table 3 ===\n")
+print("protocol,client native ms,server native ms")
+print(f"Registration,{timings['client_reg']},{timings['server_reg']}")
+print(f"Submit,{timings['client_submit']},{timings['server_submit']}")
+print(f"Update,{timings['client_update']},{timings['server_update']}")

+ 62 - 0
Scripts/run_wasm_bench

@@ -0,0 +1,62 @@
+#!/bin/bash
+
+# Usage: ./run_wasm_bench [-z] [niters]
+# Use -z to benchmark the legacy zkp code; otherwise benchmark our sigma-rs code
+# niters defaults to 10
+
+# cd into the directory containing this script (from the bash faq 028)
+if [[ $BASH_SOURCE = */* ]]; then
+  cd -- "${BASH_SOURCE%/*}/" || exit
+fi
+
+# See if we're benchmarking the legacy (zkp) code or our (sigma-rs) code
+if [ "$1" = "-z" ]; then
+    shift
+    loxdir="../application-lox-zkp"
+else
+    loxdir="../application-lox"
+fi
+
+if [ "$1" = "" ]; then
+    niters=10
+else
+    niters="$1"
+fi
+
+cleanup() {
+    echo "Cleaning up..."
+    if [ "$rdsyspid" != "" ]; then
+	kill $rdsyspid
+    fi
+    if [ "$webserverpid" != "" ]; then
+	kill $webserverpid
+    fi
+    echo "Done"
+}
+
+trap cleanup EXIT SIGINT SIGTERM
+
+# Start the rdsys backend
+cd ../rdsys
+./backend -config conf/config.json &
+rdsyspid=$!
+
+cd "$loxdir/crates/lox-wasm"
+# Touch the wasm files so that the zkp ones aren't used for the sigma-rs
+# benches or vice versa
+touch index.js index.html pkg/*
+# Start the web server, which will itself manage the lox distributor
+../../../Scripts/wasm_server $niters &
+webserverpid=$!
+
+sleep 2
+echo
+echo "*******************"
+echo "Please visit http://127.0.0.1:8000/ in a wasm-capable web browser"
+echo "*******************"
+echo
+
+wait $webserverpid
+webserverpid=""
+
+../../../Scripts/wasm_parser

+ 74 - 0
Scripts/wasm_parser

@@ -0,0 +1,74 @@
+#!/usr/bin/env python3
+
+# Usage: ./wasm_parser
+# Be sure that the uploaded_log file is in the current directory
+
+import os
+import re
+import subprocess
+import statistics
+import sys
+
+protocol = None
+protocol_map = {
+    'blockage-migration': 'Blockage Migration',
+    'check-blockage': 'Check Blockage',
+    'issue-invite': 'Issue Invite',
+    'level-up': 'Level Up',
+    'migration': 'Trust Migration',
+    'open-invite': 'Open Invitation',
+    'redeem-invite': 'Redeem Invite',
+    'trust-promo': 'Trust Promotion',
+    'update-cred': 'Update Credential',
+    'update-invite': 'Update Invite',
+}
+req_time = {}
+handle_time = {}
+inv = {}
+time = {}
+std = {}
+
+
+def parse_files(log):
+    for line in log:
+        if matched := re.match(r'Dump buffer:\s+\".*?(\S+) client (request|handle reply) time ([\d\.]+)\s*ms', line):
+            protocol = protocol_map[matched.group(1)]
+            val = matched.group(3)
+            if matched.group(2) == "request":
+                if protocol in req_time:
+                    req_time[protocol].append(float(val))
+                else:
+                    req_time.setdefault(protocol, []).append(float(val))
+            else:
+                if protocol in handle_time:
+                    handle_time[protocol].append(float(val))
+                else:
+                    handle_time.setdefault(protocol, []).append(float(val))
+    for protocol in req_time:
+        inv[protocol] = list(map(lambda x, y: x + y, handle_time[protocol], req_time[protocol]))
+        time[protocol] = sum(inv[protocol])/len(req_time[protocol])
+        std[protocol] = statistics.stdev(inv[protocol])
+
+with open("uploaded_log", "r") as uploaded:
+        parse_files(uploaded)
+
+# The order in which we output the protocols
+protocol_list = [
+    'Open Invitation',
+    'Trust Promotion',
+    'Trust Migration',
+    'Level Up',
+    'Issue Invite',
+    'Redeem Invite',
+    'Check Blockage',
+    'Blockage Migration',
+    'Update Invite',
+    'Update Credential',
+]
+
+print("\n=== Table 2 ===\n")
+print("protocol,client wasm ms")
+for p in protocol_list:
+    print(f"{p},{time[p]:.3f} ({std[p]:.3f})")
+
+

+ 78 - 0
Scripts/wasm_server

@@ -0,0 +1,78 @@
+#!/usr/bin/env python3
+
+# Portions cribbed from https://www.w3reference.com/blog/python3-http-server-post-example/
+
+from http.server import SimpleHTTPRequestHandler, HTTPServer
+import json
+import subprocess
+import sys
+import threading
+import time
+
+num_uploads = 0
+niters = 3
+httpd = None
+lox_distributor = None
+log_file = open("uploaded_log", "w")
+
+def shutdown_servers():
+    if lox_distributor is not None:
+        lox_distributor.terminate()
+    time.sleep(2)
+    httpd.shutdown()
+
+def restart_lox_distributor():
+    global lox_distributor
+    if lox_distributor is not None:
+        lox_distributor.terminate()
+    subprocess.run(["/bin/rm", "-rf", "lox_db"], cwd="../lox-distributor")
+    lox_distributor = subprocess.Popen(["/usr/bin/cargo", "run", "--release", "--features",
+                                        "test-branch"], cwd="../lox-distributor")
+    time.sleep(2)
+
+
+class PostHandler(SimpleHTTPRequestHandler):
+    def do_POST(self):
+        global num_uploads
+
+        path = self.path
+        if path != "/log":
+            self.send_response(404)
+            self.send_header('Content-Type', 'text/plain; charset=utf-8')
+            self.end_headers()
+            self.wfile.write("Bad URL for POST\n".encode('utf-8'))
+            return
+
+        content_length = int(self.headers.get('Content-Length', 0))
+        
+        post_body_bytes = self.rfile.read(content_length)
+        post_body_json_str = post_body_bytes.decode('utf-8')
+        post_body_str = json.loads(post_body_json_str)
+
+        log_file.write(post_body_str)
+        
+        restart_lox_distributor()
+
+        num_uploads += 1
+
+        if num_uploads < niters:
+            response = "0"
+        else:
+            response = "1"
+        
+        self.send_response(200)
+        self.send_header('Content-Type', 'text/plain; charset=utf-8')
+        self.end_headers()
+        self.wfile.write(response.encode('utf-8'))
+
+        if num_uploads == niters:
+            # We're done
+            threading.Thread(target=shutdown_servers).start()
+ 
+if __name__ == '__main__':
+    if len(sys.argv) > 1:
+        niters = int(sys.argv[1])
+    restart_lox_distributor()
+    server_address = ('', 8000)
+    httpd = HTTPServer(server_address, PostHandler)
+    httpd.serve_forever()

+ 3 - 0
patches/README

@@ -0,0 +1,3 @@
+The patches in this directory are just to force our crates to use the
+local copies of each other as their dependencies, instead of fetching
+them from crates.io.

+ 18 - 0
patches/application-lox-zkp.diff

@@ -0,0 +1,18 @@
+diff --git a/Cargo.toml b/Cargo.toml
+index 1a7d4ae..f0d3a1e 100644
+--- a/Cargo.toml
++++ b/Cargo.toml
+@@ -44,4 +44,12 @@ codegen-units = 16
+ opt-level = 2
+ incremental = true
+ 
+-
++[patch.crates-io]
++cmz = { path = "../cmz" }
++cmz-derive = { path = "../cmz/cmz-derive" }
++cmz-core = { path = "../cmz/cmz-core" }
++sigma-compiler = { path = "../sigma-compiler" }
++sigma-compiler-derive = { path = "../sigma-compiler/sigma-compiler-derive" }
++sigma-compiler-core = { path = "../sigma-compiler/sigma-compiler-core" }
++sigma-proofs = { path = "../sigma-proofs" }
++spongefish = { path = "../spongefish/spongefish" }

+ 18 - 0
patches/application-lox.diff

@@ -0,0 +1,18 @@
+diff --git a/Cargo.toml b/Cargo.toml
+index 4cff30a..382aaac 100644
+--- a/Cargo.toml
++++ b/Cargo.toml
+@@ -43,3 +43,13 @@ lto = "thin"
+ codegen-units = 16
+ opt-level = 2
+ incremental = true
++
++[patch.crates-io]
++cmz = { path = "../cmz" }
++cmz-derive = { path = "../cmz/cmz-derive" }
++cmz-core = { path = "../cmz/cmz-core" }
++sigma-compiler = { path = "../sigma-compiler" }
++sigma-compiler-derive = { path = "../sigma-compiler/sigma-compiler-derive" }
++sigma-compiler-core = { path = "../sigma-compiler/sigma-compiler-core" }
++sigma-proofs = { path = "../sigma-proofs" }
++spongefish = { path = "../spongefish/spongefish" }

+ 18 - 0
patches/application-ooni.diff

@@ -0,0 +1,18 @@
+diff --git a/Cargo.toml b/Cargo.toml
+index 03ae864..05c97d5 100644
+--- a/Cargo.toml
++++ b/Cargo.toml
+@@ -12,3 +12,13 @@ bincode = "1"
+ cmz = "0.2"
+ serde = "1.0.219"
+ thiserror = "2.0.12"
++
++[patch.crates-io]
++cmz = { path = "../cmz" }
++cmz-derive = { path = "../cmz/cmz-derive" }
++cmz-core = { path = "../cmz/cmz-core" }
++sigma-compiler = { path = "../sigma-compiler" }
++sigma-compiler-derive = { path = "../sigma-compiler/sigma-compiler-derive" }
++sigma-compiler-core = { path = "../sigma-compiler/sigma-compiler-core" }
++sigma-proofs = { path = "../sigma-proofs" }
++spongefish = { path = "../spongefish/spongefish" }

+ 12 - 0
patches/sigma-compiler.diff

@@ -0,0 +1,12 @@
+diff --git a/Cargo.toml b/Cargo.toml
+index b2592dc..a61769a 100644
+--- a/Cargo.toml
++++ b/Cargo.toml
+@@ -26,5 +26,5 @@ dump = [ "sigma-compiler-derive/dump" ]
+ [patch.crates-io]
+ sigma-compiler-derive = { path = "sigma-compiler-derive" }
+ sigma-compiler-core = { path = "sigma-compiler-core" }
+-# sigma-proofs = { path = "../sigma-proofs" }
+-# spongefish = { path = "../spongefish/spongefish" }
++sigma-proofs = { path = "../sigma-proofs" }
++spongefish = { path = "../spongefish/spongefish" }

+ 11 - 0
patches/sigma-proofs.diff

@@ -0,0 +1,11 @@
+diff --git a/Cargo.toml b/Cargo.toml
+index 0b99321..403c06c 100644
+--- a/Cargo.toml
++++ b/Cargo.toml
+@@ -115,3 +115,6 @@ required-features = ["std"]
+ [profile.dev]
+ # Makes tests run much faster at the cost of slightly longer builds and worse debug info.
+ opt-level = 1
++
++[patch.crates-io]
++spongefish = { path = "../spongefish/spongefish" }