104 lines
3.9 KiB
Python
Raw Normal View History

2026-02-13 01:03:47 +04:00
import hashlib
import json
2025-10-30 11:48:34 +01:00
from typing import List, Self
from pydantic import Field
from core.models import NbeSerializer
from models.transactions.transaction import Transaction
2026-04-09 23:27:30 -04:00
from node.api.serializers.operation import (
ChannelInscribeOpSerializer,
LedgerOpSerializer,
)
2025-10-30 11:48:34 +01:00
from node.api.serializers.proof import (
2026-04-09 23:27:30 -04:00
Ed25519SignatureSerializer,
2025-10-30 11:48:34 +01:00
OperationProofSerializerField,
2026-04-09 23:27:30 -04:00
ZkSignatureSerializer,
2025-10-30 11:48:34 +01:00
)
from node.api.serializers.transaction import TransactionSerializer
from utils.protocols import FromRandom
2025-10-30 11:48:34 +01:00
class SignedTransactionSerializer(NbeSerializer, FromRandom):
transaction: TransactionSerializer = Field(alias="mantle_tx", description="Transaction.")
operations_proofs: List[OperationProofSerializerField] = Field(
2026-04-09 23:27:30 -04:00
alias="ops_proofs",
description="List of OperationProof. Order should match `Self::transaction::ops`.",
2025-10-30 11:48:34 +01:00
)
2026-02-13 01:03:47 +04:00
def _compute_hash(self) -> bytes:
data = self.transaction.model_dump(mode="json")
canonical = json.dumps(data, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(canonical.encode()).digest()
2025-10-30 11:48:34 +01:00
def into_transaction(self) -> Transaction:
2026-04-09 23:27:30 -04:00
ops = self.transaction.ops
if len(ops) != len(self.operations_proofs):
2025-10-30 11:48:34 +01:00
raise ValueError(
2026-04-09 23:27:30 -04:00
f"Number of ops ({len(ops)}) does not match number of op proofs "
f"({len(self.operations_proofs)})."
2025-10-30 11:48:34 +01:00
)
2026-04-09 23:27:30 -04:00
operations: List[dict] = []
for op, proof in zip(ops, self.operations_proofs):
if isinstance(op, LedgerOpSerializer):
if not isinstance(proof, ZkSignatureSerializer):
raise ValueError(
f"Expected a ZkSig (Groth16) proof for the ledger op, got {type(proof).__name__}."
)
operations.append(
{
"content": {
"type": "LedgerTransfer",
"inputs": list(op.inputs),
"outputs": [o.into_note() for o in op.outputs],
},
"proof": {
"type": "Zk",
"signature": proof.to_bytes(),
},
}
)
elif isinstance(op, ChannelInscribeOpSerializer):
if not isinstance(proof, Ed25519SignatureSerializer):
raise ValueError(
f"Expected an Ed25519Sig proof for the channel inscribe op, got {type(proof).__name__}."
)
operations.append(
{
"content": {
"type": "ChannelInscribe",
"channel_id": op.channel_id,
"inscription": op.inscription,
"parent": op.parent,
"signer": op.signer,
},
"proof": {
"type": "Ed25519",
"signature": proof.root,
},
}
)
else:
raise ValueError(f"Unsupported mantle op type: {type(op).__name__}")
2025-10-30 11:48:34 +01:00
return Transaction.model_validate(
{
2026-02-13 01:03:47 +04:00
"hash": self._compute_hash(),
2025-10-30 11:48:34 +01:00
"operations": operations,
"execution_gas_price": self.transaction.execution_gas_price,
"storage_gas_price": self.transaction.storage_gas_price,
}
)
@classmethod
def from_random(cls) -> Self:
transaction = TransactionSerializer.from_random()
2026-04-09 23:27:30 -04:00
operations_proofs = [ZkSignatureSerializer.from_random() for _ in range(len(transaction.ops))]
2025-10-30 11:48:34 +01:00
return cls.model_validate(
2026-03-09 11:28:46 +00:00
{
"mantle_tx": transaction,
"ops_proofs": operations_proofs,
}
2025-10-30 11:48:34 +01:00
)