Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ perf
*.html

.coverage
*.ignore
**/*.ignore
30 changes: 19 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ ad = b"additional data"
Deterministic key generation from a seed:

```python
from dot_ring import Bandersnatch, secret_from_seed
from dot_ring import Bandersnatch

seed = (0).to_bytes(32, "little")
public_key, secret_scalar = secret_from_seed(seed, Bandersnatch)
public_key, secret_key = Bandersnatch.secret_from_seed(seed)
```

### Tiny VRF / IETF
Expand All @@ -72,12 +72,12 @@ from dot_ring import Bandersnatch, TinyVRF
proof = TinyVRF[Bandersnatch].prove(alpha, secret_key, ad)

# Verify
public_key = TinyVRF[Bandersnatch].get_public_key(secret_key)
public_key = Bandersnatch.public_key_from_secret(secret_key)
is_valid = proof.verify(public_key, alpha, ad)

# Serialize
proof_bytes = proof.to_bytes()
proof = TinyVRF[Bandersnatch].from_bytes(proof_bytes)
proof_bytes = proof.encode()
proof = TinyVRF[Bandersnatch].decode(proof_bytes)
```

### Pedersen VRF
Expand All @@ -95,18 +95,26 @@ is_valid = proof.verify(alpha, ad)
### Ring VRF

```python
from dot_ring import Bandersnatch, RingVRF
from dot_ring import Bandersnatch, Ring, RingRoot, RingVRF
from dot_ring.ring_proof.params import RingProofParams

# Setup ring
ring_pks = [pk1, pk2, pk3, ...] # list of public keys
ring_root = RingVRF[Bandersnatch].construct_ring_root(ring_pks)
my_pk = Bandersnatch.public_key_from_secret(secret_key)
other_pks = [
Bandersnatch.public_key_from_secret(i.to_bytes(32, "little"))
for i in range(1, 4)
]
ring_pks = [my_pk, *other_pks]

params = RingProofParams.from_ring_size(len(ring_pks), cv=Bandersnatch)
ring = Ring(ring_pks, params)
ring_root = RingRoot.from_ring(ring, params)

# Generate proof
my_pk = RingVRF[Bandersnatch].get_public_key(secret_key)
proof = RingVRF[Bandersnatch].prove(alpha, ad, secret_key, my_pk, ring_pks)
proof = RingVRF[Bandersnatch].prove(alpha, ad, secret_key, my_pk, ring, ring_root)

# Verify (proves membership without revealing which key)
is_valid = proof.verify(alpha, ad, ring_root)
is_valid = proof.verify(alpha, ad, ring, ring_root)
```

---
Expand Down
62 changes: 4 additions & 58 deletions dot_ring/__init__.py
Original file line number Diff line number Diff line change
@@ -1,61 +1,23 @@
"""
dot-ring: A Python library for Verifiable Random Functions with Additional Data (VRF-AD).
"""Public VRF, ring, and curve-suite exports."""

Supports 10+ elliptic curves including Tiny, Thin, Pedersen, and Ring VRF schemes.

Example usage:
>>> from dot_ring import Bandersnatch, TinyVRF, PedersenVRF, RingVRF
>>>
>>> # Tiny VRF
>>> proof = TinyVRF[Bandersnatch].prove(alpha, secret_key, additional_data)
>>> is_valid = proof.verify(public_key, alpha, additional_data)
>>>
>>> # Pedersen VRF
>>> proof = PedersenVRF[Bandersnatch].prove(alpha, secret_key, additional_data)
>>> is_valid = proof.verify(alpha, additional_data)
>>>
>>> # Ring VRF
>>> ring_root = RingVRF[Bandersnatch].construct_ring_root(keys_list)
>>> proof = RingVRF[Bandersnatch].prove(alpha, ad, secret_key, producer_key, keys)
>>> is_valid = proof.verify(alpha, ad, ring_root)
"""

__version__ = "0.1.0"

# =============================================================================
# VRF Implementations
# =============================================================================
# ZK-friendly curves
from dot_ring.curve.specs.baby_jubjub import BabyJubJub

# =============================================================================
# Curve Variants - Primary curves
# =============================================================================
from dot_ring.curve.specs.bandersnatch import Bandersnatch, Bandersnatch_SHAKE128
from dot_ring.curve.specs.bandersnatch_sw import Bandersnatch_SW

# BLS12-381 curves
from dot_ring.curve.specs.bls12_381_G1 import BLS12_381_G1_NU, BLS12_381_G1_RO
from dot_ring.curve.specs.bls12_381_G2 import BLS12_381_G2_NU, BLS12_381_G2_RO
from dot_ring.curve.specs.curve448 import Curve448_NU, Curve448_RO
from dot_ring.curve.specs.curve25519 import Curve25519_NU, Curve25519_RO
from dot_ring.curve.specs.ed448 import Ed448_NU, Ed448_RO
from dot_ring.curve.specs.ed25519 import Ed25519_NU, Ed25519_RO, Ed25519_TAI
from dot_ring.curve.specs.jubjub import JubJub

# NIST curves
from dot_ring.curve.specs.p256 import P256_NU, P256_RO, P256_TAI
from dot_ring.curve.specs.p384 import P384_NU, P384_RO
from dot_ring.curve.specs.p521 import P521_NU, P521_RO
from dot_ring.curve.specs.secp256k1 import Secp256k1_NU, Secp256k1_RO
from dot_ring.keygen import secret_from_seed
from dot_ring.vrf.ietf import ThinBatchVerifier, ThinVRF, TinyVRF
from dot_ring.vrf.pedersen import PedersenBatchVerifier, PedersenVRF
from dot_ring.vrf.ring import Ring, RingBatchContext, RingBatchVerifier, RingContext, RingRoot, RingRootBuilder, RingVRF
from dot_ring.vrf.ietf import ThinVRF, TinyVRF
from dot_ring.vrf.pedersen import PedersenVRF
from dot_ring.vrf.ring import Ring, RingRoot, RingVRF

# =============================================================================
# Convenience aliases
# =============================================================================
Ed25519 = Ed25519_TAI
Ed448 = Ed448_RO
Curve25519 = Curve25519_RO
Expand All @@ -67,24 +29,11 @@
BLS12_381_G1 = BLS12_381_G1_RO
BLS12_381_G2 = BLS12_381_G2_RO

# =============================================================================
# Public API
# =============================================================================
__all__ = [
# Version
"__version__",
# VRF implementations
"TinyVRF",
"ThinVRF",
"ThinBatchVerifier",
"PedersenVRF",
"PedersenBatchVerifier",
"RingVRF",
"RingContext",
"RingRootBuilder",
"RingBatchContext",
"RingBatchVerifier",
# Primary curves
"Bandersnatch",
"Bandersnatch_SHAKE128",
"Bandersnatch_SW",
Expand Down Expand Up @@ -115,17 +64,14 @@
"Secp256k1",
"Secp256k1_RO",
"Secp256k1_NU",
# ZK-friendly curves
"BabyJubJub",
"JubJub",
# BLS12-381
"BLS12_381_G1",
"BLS12_381_G1_RO",
"BLS12_381_G1_NU",
"BLS12_381_G2",
"BLS12_381_G2_RO",
"BLS12_381_G2_NU",
"secret_from_seed",
"Ring",
"RingRoot",
]
66 changes: 46 additions & 20 deletions dot_ring/curve/curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,17 @@
import hashlib
import math
from dataclasses import dataclass
from typing import TYPE_CHECKING, Generic, Literal, TypeVar, cast
from typing import Generic, Literal, TypeVar, cast, overload

from gmpy2 import invert as _invert
from gmpy2 import mpz as _mpz
from gmpy2 import powmod as _powmod

from dot_ring.curve.e2c import E2C_Variant
from dot_ring.curve.fp2 import Fp2
from dot_ring.curve.point import CurvePoint
from dot_ring.curve.specs.parameters import CurveParams, HashConstructor

if TYPE_CHECKING:
from dot_ring.curve.point import CurvePoint

CoordT = TypeVar("CoordT", int, Fp2)


Expand Down Expand Up @@ -55,6 +53,19 @@ def encoding_endian(self) -> Literal["little", "big"]:
return "little"
return self.params.encoding.endian

def valid_point(self, point: CurvePoint) -> bool:
try:
if point.is_identity() or not point.is_on_curve():
return False
cofactor = self.params.cofactor
if cofactor == 1:
return True
cofactor_inv = _powmod(cofactor, -1, self.params.subgroup_order)
cleared = point * cofactor
return not cleared.is_identity() and cleared * cofactor_inv == point
except (AttributeError, TypeError, ValueError):
return False

def _validate(self):
"""
Validate that the curve parameters are correct.
Expand Down Expand Up @@ -350,24 +361,39 @@ class CurveVariant(Generic[CoordT]):
curve: Curve[CoordT]
point_type: type[CurvePoint[Curve[CoordT], CoordT]]

def point(self, x: CoordT, y: CoordT) -> CurvePoint[Curve[CoordT], CoordT]:
return self.point_type(x, y, self.curve)

def identity(self) -> CurvePoint[Curve[CoordT], CoordT]:
return self.point_type.identity(self.curve)

def generator_point(self) -> CurvePoint[Curve[CoordT], CoordT]:
return self.point_type.generator_point(self.curve)
@overload
def point(self, x: CoordT, y: CoordT) -> CurvePoint[Curve[CoordT], CoordT]: ...

def string_to_point(self, data: str | bytes) -> CurvePoint[Curve[CoordT], CoordT]:
return self.point_type.string_to_point(data, self.curve)
@overload
def point(self, x: tuple[CoordT, CoordT]) -> CurvePoint[Curve[CoordT], CoordT]: ...

def encode_to_curve(self, alpha_string: bytes | str, salt: bytes | str = b"") -> CurvePoint[Curve[CoordT], CoordT]:
return self.point_type.encode_to_curve(alpha_string, salt, self.curve)
@overload
def point(self, x: CurvePoint[Curve[CoordT], CoordT]) -> CurvePoint[Curve[CoordT], CoordT]: ...

def msm(
def point(
self,
points: list[CurvePoint[Curve[CoordT], CoordT]],
scalars: list[int],
x: CoordT | tuple[CoordT, CoordT] | CurvePoint[Curve[CoordT], CoordT],
y: CoordT | None = None,
) -> CurvePoint[Curve[CoordT], CoordT]:
return self.point_type.msm(points, scalars, self.curve)
if isinstance(x, CurvePoint):
return x
if y is None:
x, y = x
return self.point_type(x, y)

def public_key_from_secret(self, secret_key: bytes) -> bytes:
if not isinstance(secret_key, bytes | bytearray):
raise TypeError("secret_key must be bytes")
secret_scalar = int.from_bytes(secret_key, "little")
point = self.point_type.generator_point() * secret_scalar
return point.point_to_string()

def secret_from_seed(self, seed: bytes) -> tuple[bytes, bytes]:
if not isinstance(seed, bytes | bytearray):
raise TypeError("seed must be bytes")
from dot_ring.vrf.codec import enc_scalar
from dot_ring.vrf.primitives import secret_from_seed_scalar

secret_scalar = secret_from_seed_scalar(self, bytes(seed))
secret_key = enc_scalar(self, secret_scalar)
return self.public_key_from_secret(secret_key), secret_key
22 changes: 11 additions & 11 deletions dot_ring/curve/glv.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def compute_endomorphism(self, point: AffinePointT) -> AffinePointT:
x, y, p = point.x, point.y, point.curve.params.field_modulus

if x is None or y is None:
return point.__class__.identity(point.curve)
return point.__class__.identity()
y2 = pow(cast(int, y), 2, p)
xy = (cast(int, x) * cast(int, y)) % p
f_y = (self.constant_c * (1 - y2)) % p
Expand All @@ -186,7 +186,7 @@ def compute_endomorphism(self, point: AffinePointT) -> AffinePointT:
x_a = (x_p * pow(z_p, -1, p)) % p
y_a = (y_p * pow(z_p, -1, p)) % p

return point.__class__(x_a, y_a, point.curve)
return point.__class__(x_a, y_a)

def windowed_simultaneous_mult(self, k1: int, k2: int, P1: AffinePointT, P2: AffinePointT, w: int = 2) -> AffinePointT:
"""
Expand Down Expand Up @@ -245,7 +245,7 @@ def windowed_simultaneous_mult(self, k1: int, k2: int, P1: AffinePointT, P2: Aff
# Convert back to affine
ax, ay = projective_to_affine(rx, ry, rz, p)
point_cls = cast(type[AffinePointT], P1.__class__)
return point_cls(ax, ay, P1.curve)
return point_cls(ax, ay)

def multi_scalar_mult_4(
self,
Expand Down Expand Up @@ -288,14 +288,14 @@ def multi_scalar_mult_4(

if P1.is_identity() or P2.is_identity() or P3.is_identity() or P4.is_identity():
point_cls = cast(type[AffinePointT], P1.__class__)
result = point_cls.identity(P1.curve)
result = point_cls.identity()
for scalar, point in ((k1, P1), (k2, P2), (k3, P3), (k4, P4)):
if scalar != 0 and not point.is_identity():
result = result + point * scalar # type: ignore[operator]
return result
if w != 2:
point_cls = cast(type[AffinePointT], P1.__class__)
result = point_cls.identity(P1.curve)
result = point_cls.identity()
for scalar, point in ((k1, P1), (k2, P2), (k3, P3), (k4, P4)):
if scalar:
result = result + point * scalar # type: ignore[operator]
Expand Down Expand Up @@ -347,7 +347,7 @@ def multi_scalar_mult_4(

ax, ay = projective_to_affine(rx, ry, rz, p)
point_cls = cast(type[AffinePointT], P1.__class__)
return point_cls(ax, ay, P1.curve)
return point_cls(ax, ay)

def multi_scalar_mult_pippenger(
self,
Expand All @@ -362,7 +362,7 @@ def multi_scalar_mult_pippenger(
d_coeff = points[0].curve.params.d
ax, ay = _native_signed_pippenger_msm(points, scalars, a_coeff, d_coeff, p, window_bits, True)
point_cls = cast(type[AffinePointT], points[0].__class__)
return point_cls(ax, ay, points[0].curve)
return point_cls(ax, ay)

def multi_scalar_mult_6(
self,
Expand All @@ -387,7 +387,7 @@ def multi_scalar_mult_6(

if any(point.is_identity() for point in work_points):
point_cls = cast(type[AffinePointT], work_points[0].__class__)
result = point_cls.identity(work_points[0].curve)
result = point_cls.identity()
for scalar, point in zip(work_scalars, work_points, strict=True):
if scalar != 0 and not point.is_identity():
result = result + point * scalar # type: ignore[operator]
Expand All @@ -409,7 +409,7 @@ def multi_scalar_mult_6(
or P6.y is None
):
point_cls = cast(type[AffinePointT], P1.__class__)
result = point_cls.identity(P1.curve)
result = point_cls.identity()
for scalar, point in zip(work_scalars, work_points, strict=True):
result = result + point * scalar # type: ignore[operator]
return result
Expand All @@ -427,7 +427,7 @@ def multi_scalar_mult_6(

if w != 2:
point_cls = cast(type[AffinePointT], P1.__class__)
result = point_cls.identity(P1.curve)
result = point_cls.identity()
for scalar, point in zip(work_scalars, work_points, strict=True):
result = result + point * scalar # type: ignore[operator]
return result
Expand Down Expand Up @@ -469,4 +469,4 @@ def multi_scalar_mult_6(
)
ax, ay = projective_to_affine(rx, ry, rz, p)
point_cls = cast(type[AffinePointT], P1.__class__)
return point_cls(ax, ay, P1.curve)
return point_cls(ax, ay)
Loading
Loading