Change to compressed quadratic forms for VDFs

Using compressed representation of quadratic forms reduces their
serialized size from 130 to 100 bytes (for forms with 1024-bit
discriminant). This shrinks the size of VDF outputs and VDF proofs, and
it's a breaking change as compressed representation is not compatible
with older uncompressed (a, b) representation.

Compressed forms are also used in calls to chiavdf and in timelord's
communication with VDF client.

Update to version 0.14.0 of chiavdf and version 0.2.0 of test-cache.
Older versions of these do not support compressed forms.
This commit is contained in:
Rostislav 2021-02-09 21:21:11 +02:00 committed by Gene Hoffman
parent 5052eb730c
commit 65dcae570e
6 changed files with 35 additions and 49 deletions

View File

@ -4,7 +4,7 @@ from setuptools import setup
dependencies = [
"aiter==0.13.20191203", # Used for async generator tools
"blspy==0.3.5", # Signature library
"chiavdf==0.13.4", # timelord and vdf verification
"chiavdf==0.14.0", # timelord and vdf verification
"chiabip158==0.19", # bip158-style wallet filters
"chiapos==0.12.43", # proof of space
"clvm==0.8.0",

View File

@ -31,7 +31,7 @@ from src.types.slots import (
SubSlotProofs,
)
from src.types.vdf import VDFInfo, VDFProof
from src.util.ints import uint64, uint8, int512, uint32
from src.util.ints import uint64, uint8, uint32
from src.types.sub_epoch_summary import SubEpochSummary
log = logging.getLogger(__name__)
@ -757,13 +757,10 @@ class Timelord:
writer.write((prefix + str(disc)).encode())
await writer.drain()
# Send (a, b) from 'initial_form'.
for num in [initial_form.a, initial_form.b]:
prefix_l = len(str(num))
prefix_len = len(str(prefix_l))
async with self.lock:
writer.write((str(prefix_len) + str(prefix_l) + str(num)).encode())
await writer.drain()
# Send initial_form prefixed with its length.
async with self.lock:
writer.write(bytes([len(initial_form.data)]) + initial_form.data)
await writer.drain()
try:
ok = await reader.readexactly(2)
except (asyncio.IncompleteReadError, ConnectionResetError, Exception) as e:
@ -832,10 +829,8 @@ class Timelord:
proof_bytes: bytes = stdout_bytes_io.read()
# Verifies our own proof just in case
int_size = (self.constants.DISCRIMINANT_SIZE_BITS + 16) >> 4
a = int.from_bytes(y_bytes[:int_size], "big", signed=True)
b = int.from_bytes(y_bytes[int_size:], "big", signed=True)
output = ClassgroupElement(int512(a), int512(b))
form_size = ClassgroupElement.get_size(self.constants)
output = ClassgroupElement(y_bytes[:form_size])
time_taken = time.time() - self.chain_start_time[chain]
ips = int(iterations_needed / time_taken * 10) / 10
log.info(

View File

@ -1,15 +1,26 @@
from dataclasses import dataclass
from src.util.ints import int512
from src.consensus.constants import ConsensusConstants
from src.util.streamable import Streamable, streamable
@dataclass(frozen=True)
@streamable
class ClassgroupElement(Streamable):
a: int512
b: int512
data: bytes
@staticmethod
def get_default_element():
return ClassgroupElement(int512(2), int512(1))
# Bit 3 in the first byte of serialized compressed form indicates if
# it's the default generator element.
return ClassgroupElement(b"\x08")
@staticmethod
def get_bad_element(constants: ConsensusConstants):
# Used by test_blockchain to check that bad VDF outputs and proofs are
# rejected. Use the default element for simplicity.
return ClassgroupElement(b"\x08")
@staticmethod
def get_size(constants: ConsensusConstants):
return (constants.DISCRIMINANT_SIZE_BITS + 31) // 32 * 3 + 4

View File

@ -6,7 +6,6 @@ from typing import Optional, Tuple, Dict
from chiavdf import create_discriminant
from src.types.classgroup import ClassgroupElement
from src.types.sized_bytes import bytes32
from src.util.classgroup_utils import ClassGroup
from chiavdf import verify_n_wesolowski
from src.util.ints import uint8, uint64
from src.util.streamable import Streamable, streamable
@ -69,17 +68,14 @@ class VDFProof(Streamable):
return False
try:
disc: int = get_discriminant(info.challenge, constants.DISCRIMINANT_SIZE_BITS)
# x = ClassGroup.from_ab_discriminant(input_el.a, input_el.b, disc)
y = ClassGroup.from_ab_discriminant(info.output.a, info.output.b, disc)
except Exception:
return False
# TODO: parallelize somehow, this might included multiple mini proofs (n weso)
# TODO: check for maximum witness type
return verify_n_wesolowski(
str(disc),
str(input_el.a),
str(input_el.b),
y.serialize() + bytes(self.witness),
input_el.data,
info.output.data + bytes(self.witness),
info.number_of_iterations,
constants.DISCRIMINANT_SIZE_BITS,
self.witness_type,

View File

@ -6,7 +6,7 @@ from src.consensus.constants import ConsensusConstants
from src.types.classgroup import ClassgroupElement
from src.types.sized_bytes import bytes32
from src.types.vdf import VDFProof, VDFInfo
from src.util.ints import uint64, int512, uint8
from src.util.ints import uint64, uint8
def get_vdf_info_and_proof(
@ -15,30 +15,14 @@ def get_vdf_info_and_proof(
challenge_hash: bytes32,
number_iters: uint64,
) -> Tuple[VDFInfo, VDFProof]:
int_size = (constants.DISCRIMINANT_SIZE_BITS + 16) >> 4
form_size = ClassgroupElement.get_size(constants)
result: bytes = prove(
bytes(challenge_hash),
str(vdf_input.a),
str(vdf_input.b),
vdf_input.data,
constants.DISCRIMINANT_SIZE_BITS,
number_iters,
)
output = ClassgroupElement(
int512(
int.from_bytes(
result[0:int_size],
"big",
signed=True,
)
),
int512(
int.from_bytes(
result[int_size : 2 * int_size],
"big",
signed=True,
)
),
)
proof_bytes = result[2 * int_size : 4 * int_size]
output = ClassgroupElement(result[:form_size])
proof_bytes = result[form_size : 2 * form_size]
return VDFInfo(challenge_hash, number_iters, output), VDFProof(uint8(0), proof_bytes)

View File

@ -988,7 +988,7 @@ class TestBlockHeaderValidation:
block_bad = recursive_replace(
blocks[-1],
"reward_chain_sub_block.reward_chain_sp_vdf.output",
ClassgroupElement(int512(10), int512(2)),
ClassgroupElement.get_bad_element(test_constants),
)
assert (await empty_blockchain.receive_block(block_bad))[1] == Err.INVALID_RC_SP_VDF
block_bad = recursive_replace(
@ -1032,7 +1032,7 @@ class TestBlockHeaderValidation:
block_bad = recursive_replace(
blocks[-1],
"reward_chain_sub_block.challenge_chain_sp_vdf.output",
ClassgroupElement(int512(10), int512(2)),
ClassgroupElement.get_bad_element(test_constants),
)
assert (await empty_blockchain.receive_block(block_bad))[0] == ReceiveBlockResult.INVALID_BLOCK
block_bad = recursive_replace(
@ -1321,7 +1321,7 @@ class TestBlockHeaderValidation:
block_bad = recursive_replace(
blocks[-1],
"reward_chain_sub_block.challenge_chain_ip_vdf.output",
ClassgroupElement(int512(10), int512(2)),
ClassgroupElement.get_bad_element(test_constants),
)
assert (await empty_blockchain.receive_block(block_bad))[1] == Err.INVALID_CC_IP_VDF
block_bad = recursive_replace(
@ -1351,7 +1351,7 @@ class TestBlockHeaderValidation:
block_bad = recursive_replace(
blocks[-1],
"reward_chain_sub_block.reward_chain_ip_vdf.output",
ClassgroupElement(int512(10), int512(2)),
ClassgroupElement.get_bad_element(test_constants),
)
assert (await empty_blockchain.receive_block(block_bad))[1] == Err.INVALID_RC_IP_VDF
block_bad = recursive_replace(
@ -1382,7 +1382,7 @@ class TestBlockHeaderValidation:
block_bad = recursive_replace(
blocks[-1],
"reward_chain_sub_block.infused_challenge_chain_ip_vdf.output",
ClassgroupElement(int512(10), int512(2)),
ClassgroupElement.get_bad_element(test_constants),
)
assert (await empty_blockchain.receive_block(block_bad))[1] == Err.INVALID_ICC_VDF
block_bad = recursive_replace(