Init. Commit
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
<h1 align="center">ShardSlope</h1>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/category-Crypto-blueviolet" alt="Crypto"/>
|
||||
<img src="https://img.shields.io/badge/difficulty-easy-brightgreen" alt="easy"/>
|
||||
</p>
|
||||
|
||||
В `escrow_export.json` лежат две группы долей секрета по схеме Шамира с порогом `threshold = 2`. У калибровочной группы есть две доли, у карантинной — только одна. Для карантинного секрета одной доли обычно недостаточно. Но в экспорте обе группы имеют одинаковый `rng_cache_id` — проверяем, не переиспользован ли случайный коэффициент.
|
||||
|
||||
## Решение
|
||||
|
||||
При пороге два доли — точки на прямой над конечным полем:
|
||||
|
||||
$$y = s + ax \pmod p,$$
|
||||
|
||||
где $s$ — секрет, а $a$ — случайный коэффициент наклона. Для каждого секрета коэффициент должен выбираться заново. Общий `rng_cache_id` позволяет предположить, что worker мог переиспользовать его между `calibration_batch` и `quarantine_batch`. Вычисляем коэффициент по калибровочной группе и проверяем, даст ли он осмысленный второй секрет.
|
||||
|
||||
В раздатке есть `recover.py`, который восстанавливает калибровочный секрет из двух точек. По этим же точкам вычисляем наклон.
|
||||
|
||||
$$a = (y_2-y_1)(x_2-x_1)^{-1} \pmod p.$$
|
||||
|
||||
Когда $a$ известен, одной карантинной доли $(x,y)$ уже хватает для расчёта $s = y-ax \pmod p$. Повторяем расчёт на Python:
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
data = json.loads(Path("public/escrow_export.json").read_text())
|
||||
p = int(data["field_prime"])
|
||||
first, second = data["calibration_batch"]["shares"][:2]
|
||||
x1, y1 = int(first["x"]), int(first["y"])
|
||||
x2, y2 = int(second["x"]), int(second["y"])
|
||||
slope = (y2 - y1) * pow(x2 - x1, -1, p) % p
|
||||
|
||||
share = data["quarantine_batch"]["shares"][0]
|
||||
secret = (int(share["y"]) - slope * int(share["x"])) % p
|
||||
```
|
||||
|
||||
Остаётся перевести число в байты в big-endian:
|
||||
|
||||
```python
|
||||
raw = secret.to_bytes(max(1, (secret.bit_length() + 7) // 8), "big")
|
||||
print(raw.decode())
|
||||
```
|
||||
|
||||
Получаем строку флага. Коэффициент, вычисленный по калибровочной группе, позволил восстановить карантинный секрет по одной доле.
|
||||
|
||||
[Солвер](solve/solve.py).
|
||||
|
||||
## Флаг
|
||||
|
||||
`caplag{slope_reuse_breaks_2of3_escrow}`
|
||||
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
|
||||
|
||||
def inv(value: int, prime: int) -> int:
|
||||
return pow(value % prime, -1, prime)
|
||||
|
||||
|
||||
def int_to_bytes(value: int) -> bytes:
|
||||
return value.to_bytes(max(1, (value.bit_length() + 7) // 8), "big")
|
||||
|
||||
|
||||
def recover_slope(share_a: dict, share_b: dict, prime: int) -> int:
|
||||
x1, y1 = int(share_a["x"]), int(share_a["y"])
|
||||
x2, y2 = int(share_b["x"]), int(share_b["y"])
|
||||
return ((y2 - y1) * inv(x2 - x1, prime)) % prime
|
||||
|
||||
|
||||
def recover_secret(share: dict, slope: int, prime: int) -> int:
|
||||
return (int(share["y"]) - slope * int(share["x"])) % prime
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"export",
|
||||
nargs="?",
|
||||
default="../public/escrow_export.json",
|
||||
help="path to escrow_export.json",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
data = json.loads(pathlib.Path(args.export).read_text())
|
||||
prime = int(data["field_prime"])
|
||||
calibration = data["calibration_batch"]["shares"]
|
||||
production_share = data["quarantine_batch"]["shares"][0]
|
||||
|
||||
slope = recover_slope(calibration[0], calibration[1], prime)
|
||||
secret_int = recover_secret(production_share, slope, prime)
|
||||
flag = int_to_bytes(secret_int).decode()
|
||||
if not re.fullmatch(r"caplag\{[^{}]+\}", flag):
|
||||
raise SystemExit("recovered secret is not a caplag flag")
|
||||
print(flag)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user