diff options
| author | J08nY | 2024-04-09 18:04:13 +0200 |
|---|---|---|
| committer | J08nY | 2024-04-09 18:04:13 +0200 |
| commit | 4029227ea98ed051a5d03b730a2fe04c5b6d4ef7 (patch) | |
| tree | f55a2dbec7ef43393a6bc2e55e7a0cb120efea48 | |
| parent | 7d162ae3a5758d491305dce7cc690eebc8c8454c (diff) | |
| download | pyecsca-notebook-4029227ea98ed051a5d03b730a2fe04c5b6d4ef7.tar.gz pyecsca-notebook-4029227ea98ed051a5d03b730a2fe04c5b6d4ef7.tar.zst pyecsca-notebook-4029227ea98ed051a5d03b730a2fe04c5b6d4ef7.zip | |
Extract tree eval to separate file.
| -rw-r--r-- | re/eval.py | 473 | ||||
| -rw-r--r-- | re/rpa.ipynb | 408 |
2 files changed, 543 insertions, 338 deletions
diff --git a/re/eval.py b/re/eval.py new file mode 100644 index 0000000..db5a3b0 --- /dev/null +++ b/re/eval.py @@ -0,0 +1,473 @@ +from matplotlib import pyplot as plt +import numpy as np +from scipy.stats import bernoulli +from tqdm.notebook import tqdm, trange +from pyecsca.misc.utils import TaskExecutor + + +errs = (0, 0.1, 0.2, 0.3, 0.4, 0.5) +majs = (1, 3, 5, 7, 9, 11) + +nums = (4, 10, 20, 40, 60) +smpls = (1, 2, 3, 5, 10) + +def walk_symmetric(tree, err, majority, cfg): + current = tree.root + B = bernoulli(err) + queries = 0 + while not current.is_leaf: + dmap_index = current.dmap_index + dmap_input = current.dmap_input + dmap = tree.maps[dmap_index] + true_response = dmap[cfg, dmap_input] + responses = [] + response = None + for _ in range(majority): + responses.append(true_response ^ B.rvs()) + if responses.count(True) > (majority // 2): + response = True + break + if responses.count(False) > (majority // 2): + response = False + break + response_map = {child.response: child for child in current.children} + current = response_map[response] + queries += len(responses) + return cfg in current.cfgs, len(current.cfgs), queries + + +def _eval_symmetric(tree, cfg, errs, majs, num_tries): + correct_tries = np.zeros((len(errs), len(majs))) + precise_tries = np.zeros((len(errs), len(majs))) + amount_tries = np.zeros((len(errs), len(majs))) + query_tries = np.zeros((len(errs), len(majs))) + for i, err in enumerate(errs): + for j, majority in enumerate(majs): + for _ in range(num_tries): + correct, amount, queries = walk_symmetric(tree, err, majority, cfg) + correct_tries[i, j] += correct + precise_tries[i, j] += (amount == 1) + amount_tries[i, j] += amount + query_tries[i, j] += queries + return correct_tries, precise_tries, amount_tries, query_tries + + +def eval_tree_symmetric(cfgs, build_tree, num_trees, num_tries, num_cores): + correct_tries = np.zeros((len(errs), len(majs))) + precise_tries = np.zeros((len(errs), len(majs))) + amount_tries = np.zeros((len(errs), len(majs))) + query_tries = np.zeros((len(errs), len(majs))) + + trees = [] + with TaskExecutor(max_workers=num_cores) as pool: + for i in range(num_trees): + # Build the trees + pool.submit_task((i,), build_tree, cfgs) + for (i,), future in tqdm(pool.as_completed(), total=len(pool.tasks), desc="Building trees", smoothing=0): + trees.append(future.result()) + + with TaskExecutor(max_workers=num_cores) as pool: + for i, tree in enumerate(trees): + for cfg in cfgs: + # Now cfg is the "true" config + pool.submit_task((i, cfg), _eval_symmetric, tree, cfg, errs, majs, num_tries) + for (i, cfg), future in tqdm(pool.as_completed(), total=len(pool.tasks), desc="Computing", smoothing=0): + c_tries, p_tries, a_tries, q_tries = future.result() + correct_tries += c_tries + precise_tries += p_tries + amount_tries += a_tries + query_tries += q_tries + total = num_trees * num_tries * len(cfgs) + + correct_rate = (correct_tries * 100) / total + precise_rate = (precise_tries * 100) / total + amount_rate = amount_tries / total + query_rate = query_tries / total + return correct_rate[...,::-1], precise_rate[...,::-1], amount_rate[...,::-1], query_rate[...,::-1] + + +def walk_asymmetric(tree, err_0, err_1, majority, cfg): + current = tree.root + B0 = bernoulli(err_0) + B1 = bernoulli(err_1) + queries = 0 + while not current.is_leaf: + dmap_index = current.dmap_index + dmap_input = current.dmap_input + dmap = tree.maps[dmap_index] + true_response = dmap[cfg, dmap_input] + responses = [] + response = None + for _ in range(majority): + responses.append(true_response ^ (B1.rvs() if true_response else B0.rvs())) + if responses.count(True) > (majority // 2): + response = True + break + if responses.count(False) > (majority // 2): + response = False + break + response_map = {child.response: child for child in current.children} + current = response_map[response] + queries += len(responses) + return cfg in current.cfgs, len(current.cfgs), queries + + +def _eval_asymmetric(tree, cfg, errs, majs, num_tries): + correct_tries = np.zeros((len(errs), len(errs), len(majs))) + precise_tries = np.zeros((len(errs), len(errs), len(majs))) + amount_tries = np.zeros((len(errs), len(errs), len(majs))) + query_tries = np.zeros((len(errs), len(errs), len(majs))) + + for i, err_0 in enumerate(errs): + for j, err_1 in enumerate(errs): + for k, majority in enumerate(majs): + for _ in range(num_tries): + correct, amount, queries = walk_asymmetric(tree, err_0, err_1, majority, cfg) + correct_tries[i, j, k] += correct + precise_tries[i, j, k] += (amount == 1) + amount_tries[i, j, k] += amount + query_tries[i, j, k] += queries + return correct_tries, precise_tries, amount_tries, query_tries + + +def eval_tree_asymmetric(cfgs, build_tree, num_trees, num_tries, num_cores): + correct_tries = np.zeros((len(errs), len(errs), len(majs))) + precise_tries = np.zeros((len(errs), len(errs), len(majs))) + amount_tries = np.zeros((len(errs), len(errs), len(majs))) + query_tries = np.zeros((len(errs), len(errs), len(majs))) + + trees = [] + with TaskExecutor(max_workers=num_cores) as pool: + for i in range(num_trees): + # Build the trees + pool.submit_task((i,), build_tree, cfgs) + for (i,), future in tqdm(pool.as_completed(), total=len(pool.tasks), desc="Building trees", smoothing=0): + trees.append(future.result()) + + with TaskExecutor(max_workers=num_cores) as pool: + for i, tree in enumerate(trees): + for cfg in cfgs: + # Now cfg is the "true" config + pool.submit_task((i, cfg), _eval_asymmetric, tree, cfg, errs, majs, num_tries) + for (i, cfg), future in tqdm(pool.as_completed(), total=len(pool.tasks), desc="Computing", smoothing=0): + c_tries, p_tries, a_tries, q_tries = future.result() + correct_tries += c_tries + precise_tries += p_tries + amount_tries += a_tries + query_tries += q_tries + total = num_trees * num_tries * len(cfgs) + + correct_rate = (correct_tries * 100) / total + precise_rate = (precise_tries * 100) / total + amount_rate = amount_tries / total + query_rate = query_tries / total + return correct_rate, precise_rate, amount_rate, query_rate + + +def walk_binomial(tree, num, smpl, majority, cfg): + current = tree.root + B = binom(num, 0.5) + queries = 0 + while not current.is_leaf: + dmap_index = current.dmap_index + dmap_input = current.dmap_input + dmap = tree.maps[dmap_index] + true_response = dmap[cfg, dmap_input] + responses = [true_response + B.rvs() - (num // 2) for _ in range(smpl)] + mean = np.mean(responses) + response_map = {child.response: child for child in current.children} + closest = min(response_map, key=lambda value: abs(value-mean)) + current = response_map[closest] + queries += smpl + return cfg in current.cfgs, len(current.cfgs), queries + + +def _eval_binomial(tree, cfg, nums, smpls, num_tries): + correct_tries = np.zeros((len(nums), len(smpls))) + precise_tries = np.zeros((len(nums), len(smpls))) + amount_tries = np.zeros((len(nums), len(smpls))) + query_tries = np.zeros((len(nums), len(smpls))) + + for i, num in enumerate(nums): + for j, smpl in enumerate(smpls): + for _ in range(num_tries): + correct, amount, queries = walk_binomial(tree, num, smpl, cfg) + correct_tries[i, j] += correct + precise_tries[i, j] += (amount == 1) + amount_tries[i, j] += amount + query_tries[i, j] += queries + return correct_tries, precise_tries, amount_tries, query_tries + + +def eval_tree_binomial(cfgs, build_tree, num_trees, num_tries, num_cores) + correct_tries = np.zeros((len(nums), len(smpls))) + precise_tries = np.zeros((len(nums), len(smpls))) + amount_tries = np.zeros((len(nums), len(smpls))) + query_tries = np.zeros((len(nums), len(smpls))) + + trees = [] + with TaskExecutor(max_workers=num_cores) as pool: + for i in range(num_trees): + # Build the trees + pool.submit_task((i,), build_tree, cfgs) + for (i,), future in tqdm(pool.as_completed(), total=len(pool.tasks), desc="Building trees", smoothing=0): + trees.append(future.result()) + + with TaskExecutor(max_workers=num_cores) as pool: + for i, tree in enumerate(trees): + for cfg in cfgs: + # Now cfg is the "true" config + pool.submit_task((i, cfg), _eval_binomial, tree, cfg, nums, smpls, num_tries) + for (i, cfg), future in tqdm(pool.as_completed(), total=len(pool.tasks), desc="Computing", smoothing=0): + c_tries, p_tries, a_tries, q_tries = future.result() + correct_tries += c_tries + precise_tries += p_tries + amount_tries += a_tries + query_tries += q_tries + total = num_trees * num_tries * len(cfgs) + + correct_rate = (correct_tries * 100) / total + precise_rate = (precise_tries * 100) / total + amount_rate = amount_tries / total + query_rate = query_tries / total + return correct_rate, precise_rate, amount_rate, query_rate + + +def query_rate_symmetric(query_rate): + fig, ax = plt.subplots() + im = ax.imshow(query_rate.T, cmap="plasma") + cbar_ax = fig.add_axes((0.85, 0.15, 0.04, 0.69)) + cbar = fig.colorbar(im, cax=cbar_ax) + cbar.ax.set_ylabel("Oracle query rate", rotation=-90, va="bottom") + + ax.set_xticks(np.arange(len(errs)), labels=errs) + ax.set_yticks(np.arange(len(majs)), labels=reversed(majs)) + ax.set_xlabel("error probability") + ax.set_ylabel("majority vote") + for i in range(len(errs)): + for j in range(len(majs)): + text = ax.text(i, j, f"{query_rate[i, j]:.1f}", + ha="center", va="center", color="white" if i - j <= 2 else "black") + return fig + + +def success_rate_symmetric(correct_rate, baseline): + fig, ax = plt.subplots() + im = ax.imshow(correct_rate.T, vmin=0, cmap="viridis") + cbar_ax = fig.add_axes((0.85, 0.15, 0.04, 0.69)) + cbar = fig.colorbar(im, cax=cbar_ax) + cbar.ax.set_ylabel("Success rate", rotation=-90, va="bottom") + cbar.ax.axhline(baseline, color="red", linestyle="--") + + ax.set_xticks(np.arange(len(errs)), labels=errs) + ax.set_yticks(np.arange(len(majs)), labels=reversed(majs)) + ax.set_xlabel("error probability") + ax.set_ylabel("majority vote") + for i in range(len(errs)): + for j in range(len(majs)): + c_rate = correct_rate[i, j] + text = ax.text(i, j, f"{c_rate:.1f}%", + ha="center", va="center", color="white" if c_rate < 80 else "black") + return fig + + +def precise_rate_symmetric(precise_rate): + fig, ax = plt.subplots() + im = ax.imshow(precise_rate.T, vmin=0, cmap="viridis") + cbar_ax = fig.add_axes((0.85, 0.15, 0.04, 0.69)) + cbar = fig.colorbar(im, cax=cbar_ax) + cbar.ax.set_ylabel("Precision", rotation=-90, va="bottom") + + ax.set_xticks(np.arange(len(errs)), labels=errs) + ax.set_yticks(np.arange(len(majs)), labels=reversed(majs)) + ax.set_xlabel("error probability") + ax.set_ylabel("majority vote") + for i in range(len(errs)): + for j in range(len(majs)): + p_rate = precise_rate[i, j] + text = ax.text(i, j, f"{p_rate:.1f}%", + ha="center", va="center", color="white" if p_rate < 80 else "black") + return fig + + +def success_rate_vs_query_rate_symmetric(query_rate, correct_rate): + fig, ax = plt.subplots() + ax.grid() + for i, err in enumerate(errs): + qrs = query_rate[i, :] + crs = correct_rate[i, :] + ax.scatter(qrs, crs, label=f"error = {err}") + ax.set_xlabel("oracle queries") + ax.set_ylabel("success rate") + ax.legend() + return fig + + +def success_rate_vs_majority_symmetric(correct_rate): + fig, ax = plt.subplots() + ax.grid() + for i, err in enumerate(errs): + crs = correct_rate[i, :] + ax.plot(list(reversed(majs)), crs, label=f"error = {err}") + ax.set_xlabel("majority vote") + ax.set_ylabel("success rate") + ax.set_xticks(majs) + ax.legend() + return fig + + +def query_rate_asymmetric(query_rate_b): + fig, axs = plt.subplots(nrows=2, ncols=3, sharex="col", sharey="row") + vmin = np.min(query_rate_b) + vmax = np.max(query_rate_b) + + for row in range(2): + for col in range(3): + ax = axs[row, col] + level = row * 3 + col + im = ax.imshow(query_rate_b[::-1,:,level], cmap="plasma", vmin=vmin, vmax=vmax) + ax.set_xticks(np.arange(len(errs)), labels=errs) + ax.set_yticks(np.arange(len(errs)), labels=list(reversed(errs))) + for i in range(len(errs)): + for j in range(len(errs)): + q = query_rate_b[i, len(errs) - j - 1, level] + q_rate = f"{q:.0f}" + text = ax.text(i, j, q_rate, ha="center", va="center", color="white" if q < (vmax - vmin)//2 else "black") + ax.set_xlabel("$e_1$") + ax.set_ylabel("$e_O$") + ax.set_title(majs[level]) + fig.set_size_inches((10,6)) + fig.tight_layout(h_pad=1.5, rect=(0, 0, 0.9, 1)) + cbar_ax = fig.add_axes((0.9, 0.10, 0.02, 0.84)) + cbar = fig.colorbar(im, cax=cbar_ax) + cbar.ax.set_ylabel("Oracle query rate", rotation=-90, va="bottom") + return fig + + +def success_rate_asymmetric(correct_rate_b, baseline): + fig, axs = plt.subplots(nrows=2, ncols=3, sharex="col", sharey="row") + for row in range(2): + for col in range(3): + ax = axs[row, col] + level = row * 3 + col + im = ax.imshow(correct_rate_b[::-1,:,level], cmap="viridis", vmin=0, vmax=100) + ax.set_xticks(np.arange(len(errs)), labels=errs) + ax.set_yticks(np.arange(len(errs)), labels=list(reversed(errs))) + for i in range(len(errs)): + for j in range(len(errs)): + c = correct_rate_b[i, len(errs) - j - 1, level] + c_rate = f"{c:.0f}%" + text = ax.text(i, j, c_rate, ha="center", va="center", color="white" if c < 50 else "black") + ax.set_xlabel("$e_1$") + ax.set_ylabel("$e_O$") + ax.set_title(majs[level]) + fig.set_size_inches((10,6)) + fig.tight_layout(h_pad=1.5, rect=(0, 0, 0.9, 1)) + cbar_ax = fig.add_axes((0.9, 0.10, 0.02, 0.84)) + cbar = fig.colorbar(im, cax=cbar_ax) + cbar.ax.set_ylabel("Success rate", rotation=-90, va="bottom") + cbar.ax.axhline(baseline, color="red", linestyle="--") + return fig + + +def precise_rate_asymmetric(precise_rate_b): + fig, axs = plt.subplots(nrows=2, ncols=3, sharex="col", sharey="row") + for row in range(2): + for col in range(3): + ax = axs[row, col] + level = row * 3 + col + im = ax.imshow(precise_rate_b[::-1,:,level], cmap="viridis", vmin=0, vmax=100) + ax.set_xticks(np.arange(len(errs)), labels=errs) + ax.set_yticks(np.arange(len(errs)), labels=list(reversed(errs))) + for i in range(len(errs)): + for j in range(len(errs)): + p = precise_rate_b[i, len(errs) - j - 1, level] + p_rate = f"{p:.0f}%" + text = ax.text(i, j, p_rate, ha="center", va="center", color="white" if p < 80 else "black") + ax.set_xlabel("$e_1$") + ax.set_ylabel("$e_O$") + ax.set_title(majs[level]) + fig.set_size_inches((10,6)) + fig.tight_layout(h_pad=1.5, rect=(0, 0, 0.9, 1)) + cbar_ax = fig.add_axes((0.9, 0.10, 0.02, 0.84)) + cbar = fig.colorbar(im, cax=cbar_ax) + cbar.ax.set_ylabel("Precision rate", rotation=-90, va="bottom") + return fig + + +def success_rate_vs_majority_asymmetric(correct_rate_b): + fig, ax = plt.subplots() + ax.grid() + crs_accumulated = {} + for i, err_0 in enumerate(errs): + for j, err_1 in enumerate(errs): + crs = correct_rate_b[i, j, :] + total_err = round(err_0 + err_1, 1) + l = crs_accumulated.setdefault(total_err, []) + l.append(crs) + for total_err in crs_accumulated.keys(): + crs = np.mean(crs_accumulated[total_err], axis=0) + ax.plot(majs, crs, label=f"total_error = {total_err}") + ax.set_xticks(majs) + ax.set_xlabel("majority") + ax.set_ylabel("success rate") + ax.legend(bbox_to_anchor=(1, 1.02)) + fig.tight_layout() + return fig + + +def query_rate_binomial(query_rate): + fig, ax = plt.subplots() + im = ax.imshow(query_rate.T, cmap="plasma") + cbar_ax = fig.add_axes((0.85, 0.15, 0.04, 0.69)) + cbar = fig.colorbar(im, cax=cbar_ax) + cbar.ax.set_ylabel("Oracle query rate", rotation=-90, va="bottom") + + ax.set_xticks(np.arange(len(nums)), labels=nums) + ax.set_yticks(np.arange(len(smpls)), labels=reversed(smpls)) + ax.set_xlabel("binom n") + ax.set_ylabel("samples") + for i in range(len(nums)): + for j in range(len(smpls)): + text = ax.text(i, j, f"{query_rate[i, j]:.1f}", + ha="center", va="center", color="white" if i - j <= 2 else "black") + return fig + + +def success_rate_binomial(correct_rate, baseline): + fig, ax = plt.subplots() + im = ax.imshow(correct_rate.T, vmin=0, cmap="viridis") + cbar_ax = fig.add_axes((0.85, 0.15, 0.04, 0.69)) + cbar = fig.colorbar(im, cax=cbar_ax) + cbar.ax.set_ylabel("Success rate", rotation=-90, va="bottom") + cbar.ax.axhline(baseline, color="red", linestyle="--") + + ax.set_xticks(np.arange(len(nums)), labels=nums) + ax.set_yticks(np.arange(len(smpls)), labels=reversed(smpls)) + ax.set_xlabel("binom n") + ax.set_ylabel("samples") + for i in range(len(nums)): + for j in range(len(smpls)): + c_rate = correct_rate[i, j] + text = ax.text(i, j, f"{c_rate:.1f}%", + ha="center", va="center", color="white" if c_rate < 80 else "black") + return fig + + +def precise_rate_binomial(precise_rate): + fig, ax = plt.subplots() + im = ax.imshow(precise_rate.T, vmin=0, cmap="viridis") + cbar_ax = fig.add_axes((0.85, 0.15, 0.04, 0.69)) + cbar = fig.colorbar(im, cax=cbar_ax) + cbar.ax.set_ylabel("Precision", rotation=-90, va="bottom") + + ax.set_xticks(np.arange(len(nums)), labels=nums) + ax.set_yticks(np.arange(len(smpls)), labels=reversed(smpls)) + ax.set_xlabel("binom n") + ax.set_ylabel("samples") + for i in range(len(nums)): + for j in range(len(smpls)): + p_rate = precise_rate[i, j] + text = ax.text(i, j, f"{p_rate:.1f}%", + ha="center", va="center", color="white" if p_rate < 80 else "black") + return fig diff --git a/re/rpa.ipynb b/re/rpa.ipynb index 2a8783c..f0fa1d6 100644 --- a/re/rpa.ipynb +++ b/re/rpa.ipynb @@ -11,6 +11,8 @@ " - [Exploration](#Exploration)\n", " - [Reverse-engineering](#Reverse-engineering)\n", " - [Oracle simulation](#Oracle-simulation)\n", + " - [Symmetric noise](#What-about-(symmetric)-noise?)\n", + " - [Asymmetric noise](#What-about-(asymmetric)-noise?)\n", " - [Method simulation](#Method-simulation)" ] }, @@ -53,7 +55,14 @@ "from pyecsca.ec.context import DefaultContext, local\n", "from pyecsca.sca.re.rpa import MultipleContext, rpa_distinguish, RPA\n", "from pyecsca.sca.trace import Trace\n", - "from pyecsca.sca.trace.plot import plot_trace, plot_traces" + "from pyecsca.sca.trace.plot import plot_trace, plot_traces\n", + "\n", + "from eval import (eval_tree_symmetric, eval_tree_asymmetric,\n", + " success_rate_symmetric, success_rate_asymmetric,\n", + " query_rate_symmetric, query_rate_asymmetric,\n", + " precise_rate_symmetric, precise_rate_asymmetric,\n", + " success_rate_vs_majority_symmetric, success_rate_vs_majority_asymmetric,\n", + " success_rate_vs_query_rate_symmetric)" ] }, { @@ -97,7 +106,10 @@ "g = Point(coords, X=gx, Y=gy, Z=Mod(1, p))\n", "\n", "curve = EllipticCurve(model, coords, p, infty, dict(a=a,b=b))\n", - "params = DomainParameters(curve, g, n, h)" + "params = DomainParameters(curve, g, n, h)\n", + "\n", + "# And P-256 for eval\n", + "p256 = get_params(\"secg\", \"secp256r1\", \"projective\")" ] }, { @@ -356,7 +368,6 @@ }, "outputs": [], "source": [ - "p256 = get_params(\"secg\", \"secp256r1\", \"projective\")\n", "res = rpa_distinguish(params, multipliers, simulated_oracle)" ] }, @@ -365,7 +376,7 @@ "id": "6361c477-5ddf-46ff-8918-864a453b676b", "metadata": {}, "source": [ - "Let's see if the result is correct." + "Let's see if the result is correct. You can replace the `simulated_oracle` above with `noisy_oracle(simulated_oracle, flip_proba=0.2)` or with `biased_oracle(simulated_oracle, flip_0=0.2, flip_1=0.1)` to see how the process and result changes with noise." ] }, { @@ -429,353 +440,171 @@ { "cell_type": "code", "execution_count": null, - "id": "76b0f82c-64ac-4646-91b9-99f7962200d3", + "id": "04bd8191-1af8-4183-ac95-31b1e1c48e1b", "metadata": {}, "outputs": [], "source": [ - "errs = (0, 0.1, 0.2, 0.3, 0.4, 0.5)\n", - "majs = (1, 3, 5, 7, 9, 11)\n", - "num_tries = 100" + "def build_tree(cfgs):\n", + " with silent():\n", + " re = RPA(set(cfgs))\n", + " re.build_tree(p256, tries=10)\n", + " return re.tree\n", + "\n", + "correct_rate, precise_rate, amount_rate, query_rate = eval_tree_symmetric(set(multipliers), build_tree, num_trees=100, num_tries=100, num_cores=30)" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "4e9ca09b-9fe4-4c91-ac37-530892b1df48", + "cell_type": "markdown", + "id": "9bda1baa-359a-4f9b-889d-f64e055deff6", "metadata": {}, - "outputs": [], "source": [ - "correct_tries = np.zeros((len(errs), len(majs)))\n", - "precise_tries = np.zeros((len(errs), len(majs)))\n", - "query_tries = np.zeros((len(errs), len(majs)))\n", - "total_tries = 0" + "We can plot several heatmaps:\n", + " - One for the average number of queries to the oracle.\n", + " - One for the success rate of the reverse-engineering.\n", + " - One for the precision of the reverse-engineering." ] }, { "cell_type": "code", "execution_count": null, - "id": "265eb13a-6028-4fde-9c3f-cc23768ba63e", + "id": "7be15799-d042-43ae-b052-ef7b103e1cca", "metadata": {}, "outputs": [], "source": [ - "num_cores = 30\n", - "\n", - "def measure_mult(params, multipliers, simulated_oracle, i, mult, err, majority):\n", - " correct = 0\n", - " precise = 0\n", - " calls = 0\n", - " p = lru_cache(maxsize=2)(partial(simulated_oracle, simulate_mult_id=i))\n", - " noisy = noisy_oracle(p, flip_proba=err)\n", - " def oracle(scalar, affine_point):\n", - " nonlocal calls\n", - " calls += 1\n", - " return noisy(scalar, affine_point)\n", - " re = RPA(set(multipliers))\n", - " re.build_tree(params, tries=10)\n", - " for j in range(num_tries):\n", - " res = re.run(oracle, majority=majority)\n", - " if mult in res:\n", - " correct += 1\n", - " if len(res) == 1:\n", - " precise += 1\n", - " return correct, precise, calls\n", - "\n", - "with silent(), ProcessPoolExecutor(max_workers=num_cores) as pool:\n", - " futures = []\n", - " args = []\n", - " for i, mult in enumerate(multipliers):\n", - " for err in errs:\n", - " for majority in majs:\n", - " a = (params, multipliers, simulated_oracle, i, mult, err, majority)\n", - " futures.append(pool.submit(measure_mult, *a))\n", - " args.append(a)\n", - " results = [None for _ in futures]\n", - " for future in tqdm(as_completed(futures), total=len(futures), smoothing=0):\n", - " j = futures.index(future)\n", - " a = args[j]\n", - " results[j] = future.result()" + "srs_fig = success_rate_symmetric(correct_rate, 100 / len(multipliers))" ] }, { - "cell_type": "markdown", - "id": "4d283a04-decc-422f-b804-addd57a8a635", + "cell_type": "code", + "execution_count": null, + "id": "131f2bee-4c07-4449-a380-06082861d753", "metadata": {}, + "outputs": [], "source": [ - "Now we accumulate the results across the error rate and majority vote parameters." + "qrs_fig = query_rate_symmetric(query_rate)" ] }, { "cell_type": "code", "execution_count": null, - "id": "6cc0af93-8442-4ccf-958f-3502d2d76a34", + "id": "901bfdaa-5767-4c7b-92d5-9f82dd075ea6", "metadata": {}, "outputs": [], "source": [ - "for a, result in zip(args, results):\n", - " i = errs.index(a[5])\n", - " j = len(majs) - majs.index(a[6]) - 1\n", - " correct_tries[i, j] += result[0]\n", - " precise_tries[i, j] += result[1]\n", - " query_tries[i, j] += result[2]\n", - "total_tries += num_tries\n", - "\n", - "correct_rate = (correct_tries * 100) / (total_tries * len(multipliers))\n", - "precise_rate = (precise_tries * 100) / (total_tries * len(multipliers))\n", - "query_rate = query_tries / (total_tries * len(multipliers))" + "prs_fig = precise_rate_symmetric(precise_rate)" ] }, { "cell_type": "markdown", - "id": "8c747434-84bb-4acd-a994-45a4b4859a6e", + "id": "b8eeb49e-7734-4fda-bf28-ac39f6c8a626", "metadata": {}, "source": [ - "And save the results for later." + "Another way to look at these metrics is a scatter plot." ] }, { "cell_type": "code", "execution_count": null, - "id": "857f39ad-f6ba-4006-8da9-92f4318819e2", + "id": "d5176863-d6b6-4205-8a3a-4453b8177305", "metadata": {}, "outputs": [], "source": [ - "np.save(\"rpa_re_correct_rate\", correct_rate)\n", - "np.save(\"rpa_re_precise_rate\", precise_rate)\n", - "np.save(\"rpa_re_query_rate\", query_rate)" - ] - }, - { - "cell_type": "markdown", - "id": "9bda1baa-359a-4f9b-889d-f64e055deff6", - "metadata": {}, - "source": [ - "We can plot several heatmaps:\n", - " - One for the average number of queries to the oracle.\n", - " - One for the success rate of the reverse-engineering.\n", - " - One for the precision of the reverse-engineering." + "srqrs_fig = success_rate_vs_query_rate_symmetric(query_rate, correct_rate)" ] }, { "cell_type": "code", "execution_count": null, - "id": "3caf3afd-e27e-4b6f-947b-cd8edb2d568d", + "id": "c2cfdca4-7e88-4138-b01f-696ecdcfd2bd", "metadata": {}, "outputs": [], "source": [ - "fig, ax = plt.subplots()\n", - "im = ax.imshow(query_rate.T, cmap=\"plasma\")\n", - "cbar_ax = fig.add_axes((0.85, 0.15, 0.04, 0.69))\n", - "cbar = fig.colorbar(im, cax=cbar_ax)\n", - "cbar.ax.set_ylabel(\"Oracle query rate\", rotation=-90, va=\"bottom\")\n", - "\n", - "ax.set_xticks(np.arange(len(errs)), labels=errs)\n", - "ax.set_yticks(np.arange(len(majs)), labels=reversed(majs))\n", - "ax.set_xlabel(\"error probability\")\n", - "ax.set_ylabel(\"majority vote\")\n", - "for i in range(len(errs)):\n", - " for j in range(len(majs)):\n", - " text = ax.text(i, j, f\"{query_rate[i, j]:.1f}\",\n", - " ha=\"center\", va=\"center\", color=\"w\" if i - j <= 2 else \"black\")\n", - "fig.savefig(\"rpa_re_query_rate.pdf\", bbox_inches=\"tight\")\n", - "plt.show()" + "srms_fig = success_rate_vs_majority_symmetric(correct_rate)" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "09c976dd-4940-40be-b7b2-6da73d672f24", + "cell_type": "markdown", + "id": "8c747434-84bb-4acd-a994-45a4b4859a6e", "metadata": {}, - "outputs": [], "source": [ - "fig, ax = plt.subplots()\n", - "im = ax.imshow(correct_rate.T, vmin=0, cmap=\"viridis\")\n", - "cbar_ax = fig.add_axes((0.85, 0.15, 0.04, 0.69))\n", - "cbar = fig.colorbar(im, cax=cbar_ax)\n", - "cbar.ax.set_ylabel(\"Success rate\", rotation=-90, va=\"bottom\")\n", - "cbar.ax.axhline(100 / len(multipliers), color=\"red\", linestyle=\"--\")\n", - "\n", - "ax.set_xticks(np.arange(len(errs)), labels=errs)\n", - "ax.set_yticks(np.arange(len(majs)), labels=reversed(majs))\n", - "ax.set_xlabel(\"error probability\")\n", - "ax.set_ylabel(\"majority vote\")\n", - "for i in range(len(errs)):\n", - " for j in range(len(majs)):\n", - " c_rate = correct_rate[i, j]\n", - " text = ax.text(i, j, f\"{c_rate:.1f}%\",\n", - " ha=\"center\", va=\"center\", color=\"w\" if c_rate < 80 else \"black\")\n", - "fig.savefig(\"rpa_re_success_rate.pdf\", bbox_inches=\"tight\")\n", - "plt.show()" + "And save the results for later." ] }, { "cell_type": "code", "execution_count": null, - "id": "56114e99-e949-488f-85bb-f6bd2e221d39", + "id": "857f39ad-f6ba-4006-8da9-92f4318819e2", "metadata": {}, "outputs": [], "source": [ - "fig, ax = plt.subplots()\n", - "im = ax.imshow(precise_rate.T, vmin=0, cmap=\"viridis\")\n", - "cbar_ax = fig.add_axes((0.85, 0.15, 0.04, 0.69))\n", - "cbar = fig.colorbar(im, cax=cbar_ax)\n", - "cbar.ax.set_ylabel(\"Precision\", rotation=-90, va=\"bottom\")\n", - "\n", - "ax.set_xticks(np.arange(len(errs)), labels=errs)\n", - "ax.set_yticks(np.arange(len(majs)), labels=reversed(majs))\n", - "ax.set_xlabel(\"error probability\")\n", - "ax.set_ylabel(\"majority vote\")\n", - "for i in range(len(errs)):\n", - " for j in range(len(majs)):\n", - " p_rate = precise_rate[i, j]\n", - " text = ax.text(i, j, f\"{p_rate:.1f}%\",\n", - " ha=\"center\", va=\"center\", color=\"w\" if p_rate < 80 else \"black\")\n", - "fig.savefig(\"rpa_re_precision.pdf\", bbox_inches=\"tight\")\n", - "plt.show()" + "np.save(\"rpa_re_correct_rate\", correct_rate)\n", + "np.save(\"rpa_re_precise_rate\", precise_rate)\n", + "np.save(\"rpa_re_query_rate\", query_rate)" ] }, { "cell_type": "markdown", - "id": "b8eeb49e-7734-4fda-bf28-ac39f6c8a626", + "id": "e3981f44-ed3b-43f5-b4bd-e2d4b2ff95e8", "metadata": {}, "source": [ - "Another way to look at these metrics is a scatter plot." + "#### What about (asymmetric) noise?\n", + "The oracle may not only be noisy, but biased, this computation evaluates that case. Beware, for the same parameters this is about 6x slower because of the other dimension (two error probabilities instead of one)." ] }, { "cell_type": "code", "execution_count": null, - "id": "1e1b1769-6aa9-4b95-a7b7-dd5cfd501d27", + "id": "06268c58-63c9-4565-ba52-4414a9939581", "metadata": {}, "outputs": [], "source": [ - "fig, ax = plt.subplots()\n", - "ax.grid()\n", - "for i, err in enumerate(errs):\n", - " qrs = query_rate[i, :]\n", - " crs = correct_rate[i, :]\n", - " ax.scatter(qrs, crs, label=f\"error = {err}\")\n", - "ax.set_xlabel(\"oracle queries\")\n", - "ax.set_ylabel(\"success rate\")\n", - "ax.legend()\n", - "fig.savefig(\"rpa_re_scatter.pdf\", bbox_inches=\"tight\")\n", - "plt.show()" + "def build_tree(cfgs):\n", + " with silent():\n", + " re = RPA(set(cfgs))\n", + " re.build_tree(p256, tries=10)\n", + " return re.tree\n", + "\n", + "correct_rate_b, precise_rate_b, amount_rate_b, query_rate_b = eval_tree_asymmetric(set(multipliers), build_tree, num_trees=100, num_tries=100, num_cores=30)" ] }, { "cell_type": "code", "execution_count": null, - "id": "921b1c7b-fd40-415e-b737-1a85e07a9ba5", + "id": "43a0055a-8d62-4f0a-be9f-e6ccb47b1d11", "metadata": {}, "outputs": [], "source": [ - "fig, ax = plt.subplots()\n", - "for i, err in enumerate(errs):\n", - " crs = correct_rate[i, :]\n", - " ax.plot(list(reversed(majs)), crs, label=f\"error = {err}\")\n", - "ax.set_xlabel(\"majority vote\")\n", - "ax.set_ylabel(\"success rate\")\n", - "ax.set_xticks(majs)\n", - "ax.legend()\n", - "fig.savefig(\"rpa_re_plot.pdf\", bbox_inches=\"tight\")\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "e3981f44-ed3b-43f5-b4bd-e2d4b2ff95e8", - "metadata": {}, - "source": [ - "#### What about (asymmetric) noise?" + "sra_fig = success_rate_asymmetric(correct_rate_b, 100 / len(multipliers))" ] }, { "cell_type": "code", "execution_count": null, - "id": "40652fa3-39f9-47ad-9424-ee2fdaac78d3", + "id": "6b14a5eb-cfde-4e3e-a4f3-0d21dcca2dff", "metadata": {}, "outputs": [], "source": [ - "correct_tries_b = np.zeros((len(errs), len(errs), len(majs)))\n", - "precise_tries_b = np.zeros((len(errs), len(errs), len(majs)))\n", - "query_tries_b = np.zeros((len(errs), len(errs), len(majs)))\n", - "total_tries_b = 0\n", - "\n", - "num_tries_b = 100" + "qra_fig = query_rate_asymmetric(query_rate_b)" ] }, { "cell_type": "code", "execution_count": null, - "id": "d6dce31a-1369-4556-8e01-4e19ad14d21e", + "id": "c5c7fe0e-88be-4d4e-a8fb-352ea85060f1", "metadata": {}, "outputs": [], "source": [ - "num_cores = 30\n", - "\n", - "def measure_mult(params, multipliers, simulated_oracle, i, mult, err_0, err_1, majority):\n", - " correct = 0\n", - " precise = 0\n", - " calls = 0\n", - " p = lru_cache(maxsize=2)(partial(simulated_oracle, simulate_mult_id=i))\n", - " biased = biased_oracle(p, flip_0=err_0, flip_1=err_1)\n", - " def oracle(scalar, affine_point):\n", - " nonlocal calls\n", - " calls += 1\n", - " return biased(scalar, affine_point)\n", - " re = RPA(set(multipliers))\n", - " re.build_tree(params, tries=10)\n", - " for j in range(num_tries_b):\n", - " res = re.run(oracle, majority=majority)\n", - " if mult in res:\n", - " correct += 1\n", - " if len(res) == 1:\n", - " precise += 1\n", - " return correct, precise, calls\n", - "\n", - "with silent(), ProcessPoolExecutor(max_workers=num_cores) as pool:\n", - " futures = []\n", - " args = []\n", - " for i, mult in enumerate(multipliers):\n", - " for err_0 in errs:\n", - " for err_1 in errs:\n", - " for majority in majs:\n", - " a = (params, multipliers, simulated_oracle, i, mult, err_0, err_1, majority)\n", - " futures.append(pool.submit(measure_mult, *a))\n", - " args.append(a)\n", - " results = [None for _ in futures]\n", - " for future in tqdm(as_completed(futures), total=len(futures), smoothing=0):\n", - " j = futures.index(future)\n", - " a = args[j]\n", - " results[j] = future.result()" - ] - }, - { - "cell_type": "markdown", - "id": "f4ddde62-18bb-46b2-a2e2-565cc21d654f", - "metadata": {}, - "source": [ - "Now we accumulate the results across the error rate and majority vote parameters." + "pra_fig = precise_rate_asymmetric(precise_rate_b)" ] }, { "cell_type": "code", "execution_count": null, - "id": "20b9ac29-401f-4068-8b2a-4b2f14e14b28", + "id": "e4e999bf-bdd0-4b9f-86c6-1fdee773fd27", "metadata": {}, "outputs": [], "source": [ - "for a, result in zip(args, results):\n", - " i = errs.index(a[5])\n", - " j = errs.index(a[6])\n", - " k = majs.index(a[7])\n", - " correct_tries_b[i, j, k] += result[0]\n", - " precise_tries_b[i, j, k] += result[1]\n", - " query_tries_b[i, j, k] += result[2]\n", - "total_tries_b += num_tries_b\n", - "\n", - "correct_rate_b = (correct_tries_b * 100) / (total_tries_b * len(multipliers))\n", - "precise_rate_b = (precise_tries_b * 100) / (total_tries_b * len(multipliers))\n", - "query_rate_b = query_tries_b / (total_tries_b * len(multipliers))" + "srma_fig = success_rate_vs_majority_asymmetric(correct_rate_b)" ] }, { @@ -799,103 +628,6 @@ ] }, { - "cell_type": "code", - "execution_count": null, - "id": "5fd78523-0463-4932-ae64-42a444104d2c", - "metadata": {}, - "outputs": [], - "source": [ - "fig, axs = plt.subplots(nrows=2, ncols=3, sharex=\"col\", sharey=\"row\")\n", - "vmin = np.min(query_rate_b)\n", - "vmax = np.max(query_rate_b)\n", - "\n", - "for row in range(2):\n", - " for col in range(3):\n", - " ax = axs[row, col]\n", - " level = row * 3 + col\n", - " im = ax.imshow(query_rate_b[::-1,:,level], cmap=\"plasma\", vmin=vmin, vmax=vmax)\n", - " ax.set_xticks(np.arange(len(errs)), labels=errs)\n", - " ax.set_yticks(np.arange(len(errs)), labels=list(reversed(errs)))\n", - " for i in range(len(errs)):\n", - " for j in range(len(errs)):\n", - " q_rate = f\"{query_rate_b[i, len(errs) - j - 1, level]:.0f}\"\n", - " loc = f\"{errs[i]} {errs[j]}\"\n", - " text = ax.text(i, j, q_rate, ha=\"center\", va=\"center\")\n", - " ax.set_xlabel(\"$e_1$\")\n", - " ax.set_ylabel(\"$e_O$\")\n", - " ax.set_title(majs[level])\n", - "fig.set_size_inches((10,6))\n", - "fig.tight_layout(h_pad=1.5, rect=(0, 0, 0.9, 1))\n", - "cbar_ax = fig.add_axes((0.9, 0.10, 0.02, 0.84))\n", - "cbar = fig.colorbar(im, cax=cbar_ax)\n", - "cbar.ax.set_ylabel(\"Oracle query rate\", rotation=-90, va=\"bottom\")\n", - "fig.savefig(\"rpa_re_asymmetric_query_rate.pdf\")\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6b976938-f10d-439b-83b2-94d20d00ecbc", - "metadata": {}, - "outputs": [], - "source": [ - "fig, axs = plt.subplots(nrows=2, ncols=3, sharex=\"col\", sharey=\"row\")\n", - "for row in range(2):\n", - " for col in range(3):\n", - " ax = axs[row, col]\n", - " level = row * 3 + col\n", - " im = ax.imshow(correct_rate_b[::-1,:,level], cmap=\"viridis\", vmin=0, vmax=100)\n", - " ax.set_xticks(np.arange(len(errs)), labels=errs)\n", - " ax.set_yticks(np.arange(len(errs)), labels=list(reversed(errs)))\n", - " for i in range(len(errs)):\n", - " for j in range(len(errs)):\n", - " c = correct_rate_b[i, len(errs) - j - 1, level]\n", - " c_rate = f\"{c:.0f}%\"\n", - " loc = f\"{errs[i]} {errs[j]}\"\n", - " text = ax.text(i, j, c_rate, ha=\"center\", va=\"center\", color=\"w\" if c < 50 else \"black\")\n", - " ax.set_xlabel(\"$e_1$\")\n", - " ax.set_ylabel(\"$e_O$\")\n", - " ax.set_title(majs[level])\n", - "fig.set_size_inches((10,6))\n", - "fig.tight_layout(h_pad=1.5, rect=(0, 0, 0.9, 1))\n", - "cbar_ax = fig.add_axes((0.9, 0.10, 0.02, 0.84))\n", - "cbar = fig.colorbar(im, cax=cbar_ax)\n", - "cbar.ax.set_ylabel(\"Success rate\", rotation=-90, va=\"bottom\")\n", - "cbar.ax.axhline(100 / len(multipliers), color=\"red\", linestyle=\"--\")\n", - "fig.savefig(\"rpa_re_asymmetric_success_rate.pdf\")\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "868c4a7a-5c0f-4ee6-9a1a-2c251dfae34c", - "metadata": {}, - "outputs": [], - "source": [ - "fig, ax = plt.subplots()\n", - "ax.grid()\n", - "crs_accumulated = {}\n", - "for i, err_0 in enumerate(errs):\n", - " for j, err_1 in enumerate(errs):\n", - " crs = correct_rate_b[i, j, :]\n", - " total_err = round(err_0 + err_1, 1)\n", - " l = crs_accumulated.setdefault(total_err, [])\n", - " l.append(crs)\n", - " #ax.scatter(majs, crs, label=str(err_0 + err_1))\n", - "for total_err in crs_accumulated.keys():\n", - " crs = np.mean(crs_accumulated[total_err], axis=0)\n", - " ax.plot(majs, crs, label=f\"total_error = {total_err}\")\n", - "ax.set_xticks(majs)\n", - "ax.set_xlabel(\"majority\")\n", - "ax.set_ylabel(\"success rate\")\n", - "ax.legend(bbox_to_anchor=(1, 1.02))\n", - "fig.tight_layout()\n", - "plt.show()" - ] - }, - { "cell_type": "markdown", "id": "62b0b8f2-8149-4abd-9aa7-a056b237ac6e", "metadata": {}, |
