2025-10-30 11:48:34 +01:00
|
|
|
from abc import ABC, abstractmethod
|
2026-06-12 16:23:04 -03:00
|
|
|
from typing import Annotated, Any, Optional, Self, Union
|
2025-10-30 11:48:34 +01:00
|
|
|
|
2026-02-13 01:03:47 +04:00
|
|
|
from pydantic import BeforeValidator, Field, RootModel
|
2025-10-30 11:48:34 +01:00
|
|
|
|
|
|
|
|
from core.models import NbeSerializer
|
|
|
|
|
from models.transactions.operations.proofs import (
|
|
|
|
|
Ed25519Signature,
|
|
|
|
|
NbeSignature,
|
2026-06-12 16:23:04 -03:00
|
|
|
UnknownSignature,
|
2025-10-30 11:48:34 +01:00
|
|
|
ZkAndEd25519Signature,
|
|
|
|
|
ZkSignature,
|
|
|
|
|
)
|
2026-06-12 16:23:04 -03:00
|
|
|
from node.api.serializers.fields import BytesFromHex, BytesFromHexOrIntArray
|
2025-10-30 11:48:34 +01:00
|
|
|
from utils.protocols import EnforceSubclassFromRandom
|
|
|
|
|
from utils.random import random_bytes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class OperationProofSerializer(EnforceSubclassFromRandom, ABC):
|
|
|
|
|
@abstractmethod
|
|
|
|
|
def into_operation_proof(cls) -> NbeSignature:
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
2026-02-13 01:03:47 +04:00
|
|
|
class Ed25519SignatureSerializer(OperationProofSerializer, RootModel[bytes]):
|
2026-06-12 16:23:04 -03:00
|
|
|
root: BytesFromHexOrIntArray
|
2025-10-30 11:48:34 +01:00
|
|
|
|
|
|
|
|
def into_operation_proof(self) -> NbeSignature:
|
|
|
|
|
return Ed25519Signature.model_validate(
|
|
|
|
|
{
|
|
|
|
|
"signature": self.root,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def from_random(cls, *args, **kwargs) -> Self:
|
2026-02-13 01:03:47 +04:00
|
|
|
return cls.model_validate(list(random_bytes(64)))
|
2025-10-30 11:48:34 +01:00
|
|
|
|
|
|
|
|
|
2026-04-09 23:27:30 -04:00
|
|
|
class ZkSignatureSerializer(OperationProofSerializer, NbeSerializer):
|
|
|
|
|
"""Groth16 ZK proof: pi_a (32B) + pi_b (64B) + pi_c (32B) = 128 bytes total."""
|
|
|
|
|
|
2026-06-12 16:23:04 -03:00
|
|
|
pi_a: BytesFromHexOrIntArray
|
|
|
|
|
pi_b: BytesFromHexOrIntArray
|
|
|
|
|
pi_c: BytesFromHexOrIntArray
|
2026-04-09 23:27:30 -04:00
|
|
|
|
|
|
|
|
def to_bytes(self) -> bytes:
|
|
|
|
|
return self.pi_a + self.pi_b + self.pi_c
|
2025-10-30 11:48:34 +01:00
|
|
|
|
|
|
|
|
def into_operation_proof(self) -> NbeSignature:
|
|
|
|
|
return ZkSignature.model_validate(
|
|
|
|
|
{
|
2026-04-09 23:27:30 -04:00
|
|
|
"signature": self.to_bytes(),
|
2025-10-30 11:48:34 +01:00
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def from_random(cls, *args, **kwargs) -> Self:
|
2026-04-09 23:27:30 -04:00
|
|
|
return cls.model_validate(
|
|
|
|
|
{
|
|
|
|
|
"pi_a": list(random_bytes(32)),
|
|
|
|
|
"pi_b": list(random_bytes(64)),
|
|
|
|
|
"pi_c": list(random_bytes(32)),
|
|
|
|
|
}
|
|
|
|
|
)
|
2025-10-30 11:48:34 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class ZkAndEd25519SignaturesSerializer(OperationProofSerializer, NbeSerializer):
|
2026-05-06 18:31:16 -04:00
|
|
|
zk_signature: ZkSignatureSerializer = Field(alias="zk_sig")
|
|
|
|
|
ed25519_signature: BytesFromHex = Field(alias="ed25519_sig")
|
2025-10-30 11:48:34 +01:00
|
|
|
|
|
|
|
|
def into_operation_proof(self) -> NbeSignature:
|
|
|
|
|
return ZkAndEd25519Signature.model_validate(
|
|
|
|
|
{
|
2026-05-06 18:31:16 -04:00
|
|
|
"zk_signature": self.zk_signature.to_bytes(),
|
2025-10-30 11:48:34 +01:00
|
|
|
"ed25519_signature": self.ed25519_signature,
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def from_random(cls, *args, **kwargs) -> Self:
|
|
|
|
|
return ZkAndEd25519SignaturesSerializer.model_validate(
|
|
|
|
|
{
|
2026-05-06 18:31:16 -04:00
|
|
|
"zk_sig": {
|
|
|
|
|
"pi_a": list(random_bytes(32)),
|
|
|
|
|
"pi_b": list(random_bytes(64)),
|
|
|
|
|
"pi_c": list(random_bytes(32)),
|
|
|
|
|
},
|
|
|
|
|
"ed25519_sig": random_bytes(64).hex(),
|
2025-10-30 11:48:34 +01:00
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2026-06-12 16:23:04 -03:00
|
|
|
class UnknownProofSerializer(OperationProofSerializer, NbeSerializer):
|
|
|
|
|
"""Fallback for proof variants without a typed serializer (e.g. NoProof).
|
|
|
|
|
|
|
|
|
|
Preserves the raw value verbatim so unknown proof types never break block
|
|
|
|
|
ingestion.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
raw: Optional[Any] = None
|
|
|
|
|
|
|
|
|
|
def into_operation_proof(self) -> NbeSignature:
|
|
|
|
|
return UnknownSignature.model_validate({"raw": self.raw})
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def from_random(cls, *args, **kwargs) -> Self:
|
|
|
|
|
return cls.model_validate({"raw": "NoProof"})
|
|
|
|
|
|
|
|
|
|
|
2026-02-13 01:03:47 +04:00
|
|
|
PROOF_TAG_TO_SERIALIZER = {
|
|
|
|
|
"Ed25519Sig": Ed25519SignatureSerializer,
|
|
|
|
|
"ZkSig": ZkSignatureSerializer,
|
|
|
|
|
"ZkAndEd25519Sigs": ZkAndEd25519SignaturesSerializer,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _parse_proof(data: Any) -> OperationProofSerializer:
|
|
|
|
|
if isinstance(data, OperationProofSerializer):
|
|
|
|
|
return data
|
|
|
|
|
if isinstance(data, dict):
|
|
|
|
|
for tag, serializer_class in PROOF_TAG_TO_SERIALIZER.items():
|
|
|
|
|
if tag in data:
|
|
|
|
|
return serializer_class.model_validate(data[tag])
|
2026-06-12 16:23:04 -03:00
|
|
|
# Unit variants (e.g. "NoProof") arrive as plain strings; unknown tagged
|
|
|
|
|
# variants arrive as dicts that matched no known tag. Keep them verbatim.
|
|
|
|
|
return UnknownProofSerializer.model_validate({"raw": data})
|
2026-02-13 01:03:47 +04:00
|
|
|
|
|
|
|
|
|
2025-10-30 11:48:34 +01:00
|
|
|
OperationProofSerializerVariants = Union[
|
2026-06-12 16:23:04 -03:00
|
|
|
Ed25519SignatureSerializer, ZkSignatureSerializer, ZkAndEd25519SignaturesSerializer, UnknownProofSerializer
|
2025-10-30 11:48:34 +01:00
|
|
|
]
|
2026-02-13 01:03:47 +04:00
|
|
|
OperationProofSerializerField = Annotated[
|
|
|
|
|
OperationProofSerializerVariants,
|
|
|
|
|
BeforeValidator(_parse_proof),
|
|
|
|
|
]
|