Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New aggregate signature conditions #15769

Merged
merged 12 commits into from
Jul 20, 2023
9 changes: 9 additions & 0 deletions chia/simulator/block_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -1785,6 +1785,15 @@ def compute_cost_test(
elif hard_fork and condition == ConditionOpcode.SOFTFORK.value:
arg = cond.rest().first().as_int()
condition_cost += arg * 10000
elif hard_fork and condition in [
ConditionOpcode.AGG_SIG_PARENT,
ConditionOpcode.AGG_SIG_PUZZLE,
ConditionOpcode.AGG_SIG_AMOUNT,
ConditionOpcode.AGG_SIG_PUZZLE_AMOUNT,
ConditionOpcode.AGG_SIG_PARENT_AMOUNT,
ConditionOpcode.AGG_SIG_PARENT_PUZZLE,
]:
condition_cost += ConditionCost.AGG_SIG.value
arvidn marked this conversation as resolved.
Show resolved Hide resolved
return None, uint64(clvm_cost + size_cost + condition_cost)
except Exception:
return uint16(Err.GENERATOR_RUNTIME_ERROR.value), uint64(0)
Expand Down
50 changes: 48 additions & 2 deletions chia/simulator/wallet_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from chia.types.condition_opcodes import ConditionOpcode
from chia.types.condition_with_args import ConditionWithArgs
from chia.types.spend_bundle import SpendBundle
from chia.util.condition_tools import conditions_dict_for_solution
from chia.util.condition_tools import agg_sig_additional_data, conditions_dict_for_solution
from chia.util.hash import std_hash
from chia.util.ints import uint32, uint64
from chia.wallet.derive_keys import master_sk_to_wallet_sk
Expand Down Expand Up @@ -177,6 +177,7 @@ def generate_unsigned_transaction(

def sign_transaction(self, coin_spends: List[CoinSpend]) -> SpendBundle:
signatures = []
data = agg_sig_additional_data(self.constants.AGG_SIG_ME_ADDITIONAL_DATA)
for coin_spend in coin_spends: # noqa
secret_key = self.get_private_key_for_puzzle_hash(coin_spend.coin.puzzle_hash)
synthetic_secret_key = calculate_synthetic_secret_key(secret_key, DEFAULT_HIDDEN_PUZZLE_HASH)
Expand All @@ -189,8 +190,53 @@ def sign_transaction(self, coin_spends: List[CoinSpend]) -> SpendBundle:
signature = AugSchemeMPL.sign(synthetic_secret_key, msg)
signatures.append(signature)

for cwa in conditions_dict.get(ConditionOpcode.AGG_SIG_PARENT, []):
msg = cwa.vars[1] + bytes(coin_spend.coin.parent_coin_info) + data[ConditionOpcode.AGG_SIG_PARENT]
signature = AugSchemeMPL.sign(synthetic_secret_key, msg)
signatures.append(signature)

for cwa in conditions_dict.get(ConditionOpcode.AGG_SIG_PUZZLE, []):
msg = cwa.vars[1] + bytes(coin_spend.coin.puzzle_hash) + data[ConditionOpcode.AGG_SIG_PUZZLE]
signature = AugSchemeMPL.sign(synthetic_secret_key, msg)
signatures.append(signature)

for cwa in conditions_dict.get(ConditionOpcode.AGG_SIG_AMOUNT, []):
msg = cwa.vars[1] + int_to_bytes(coin_spend.coin.amount) + data[ConditionOpcode.AGG_SIG_AMOUNT]
signature = AugSchemeMPL.sign(synthetic_secret_key, msg)
signatures.append(signature)

for cwa in conditions_dict.get(ConditionOpcode.AGG_SIG_PUZZLE_AMOUNT, []):
msg = (
cwa.vars[1]
+ bytes(coin_spend.coin.puzzle_hash)
+ int_to_bytes(coin_spend.coin.amount)
+ data[ConditionOpcode.AGG_SIG_PUZZLE_AMOUNT]
)
signature = AugSchemeMPL.sign(synthetic_secret_key, msg)
signatures.append(signature)

for cwa in conditions_dict.get(ConditionOpcode.AGG_SIG_PARENT_AMOUNT, []):
msg = (
cwa.vars[1]
+ bytes(coin_spend.coin.parent_coin_info)
+ int_to_bytes(coin_spend.coin.amount)
+ data[ConditionOpcode.AGG_SIG_PARENT_AMOUNT]
)
signature = AugSchemeMPL.sign(synthetic_secret_key, msg)
signatures.append(signature)

for cwa in conditions_dict.get(ConditionOpcode.AGG_SIG_PARENT_PUZZLE, []):
msg = (
cwa.vars[1]
+ bytes(coin_spend.coin.parent_coin_info)
+ bytes(coin_spend.coin.puzzle_hash)
+ data[ConditionOpcode.AGG_SIG_PARENT_PUZZLE]
)
signature = AugSchemeMPL.sign(synthetic_secret_key, msg)
signatures.append(signature)

for cwa in conditions_dict.get(ConditionOpcode.AGG_SIG_ME, []):
msg = cwa.vars[1] + bytes(coin_spend.coin.name()) + self.constants.AGG_SIG_ME_ADDITIONAL_DATA
msg = cwa.vars[1] + bytes(coin_spend.coin.name()) + data[ConditionOpcode.AGG_SIG_ME]
signature = AugSchemeMPL.sign(synthetic_secret_key, msg)
signatures.append(signature)

Expand Down
11 changes: 10 additions & 1 deletion chia/types/coin_spend.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,16 @@ def compute_additions_with_cost(
raise ValidationError(Err.BLOCK_COST_EXCEEDS_MAX, "compute_additions() for CoinSpend")
atoms = cond.as_iter()
op = next(atoms).atom
if op in [ConditionOpcode.AGG_SIG_ME, ConditionOpcode.AGG_SIG_UNSAFE]:
if op in [
wallentx marked this conversation as resolved.
Show resolved Hide resolved
ConditionOpcode.AGG_SIG_PARENT,
ConditionOpcode.AGG_SIG_PUZZLE,
ConditionOpcode.AGG_SIG_AMOUNT,
ConditionOpcode.AGG_SIG_PUZZLE_AMOUNT,
ConditionOpcode.AGG_SIG_PARENT_AMOUNT,
ConditionOpcode.AGG_SIG_PARENT_PUZZLE,
ConditionOpcode.AGG_SIG_UNSAFE,
ConditionOpcode.AGG_SIG_ME,
]:
cost += ConditionCost.AGG_SIG.value
continue
if op != ConditionOpcode.CREATE_COIN.value:
Expand Down
6 changes: 6 additions & 0 deletions chia/types/condition_opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ class ConditionOpcode(bytes, enum.Enum):

# the conditions below require bls12-381 signatures

AGG_SIG_PARENT = bytes([43])
AGG_SIG_PUZZLE = bytes([44])
AGG_SIG_AMOUNT = bytes([45])
AGG_SIG_PUZZLE_AMOUNT = bytes([46])
AGG_SIG_PARENT_AMOUNT = bytes([47])
AGG_SIG_PARENT_PUZZLE = bytes([48])
AGG_SIG_UNSAFE = bytes([49])
AGG_SIG_ME = bytes([50])

Expand Down
151 changes: 133 additions & 18 deletions chia/util/condition_tools.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from __future__ import annotations

from functools import lru_cache
from typing import Dict, List, Tuple

from clvm.casts import int_from_bytes
from clvm.casts import int_from_bytes, int_to_bytes

from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program
Expand All @@ -12,11 +13,9 @@
from chia.types.condition_with_args import ConditionWithArgs
from chia.types.spend_bundle_conditions import SpendBundleConditions
from chia.util.errors import ConsensusError, Err
from chia.util.hash import std_hash
from chia.util.ints import uint64

# TODO: review each `assert` and consider replacing with explicit checks
# since asserts can be stripped with python `-OO` flag


def parse_sexp_to_condition(sexp: Program) -> ConditionWithArgs:
"""
Expand Down Expand Up @@ -55,41 +54,157 @@ def parse_sexp_to_conditions(sexp: Program) -> List[ConditionWithArgs]:
return [parse_sexp_to_condition(s) for s in sexp.as_iter()]


@lru_cache
def agg_sig_additional_data(agg_sig_data: bytes) -> Dict[ConditionOpcode, bytes]:
ret: Dict[ConditionOpcode, bytes] = {}
for code in [
ConditionOpcode.AGG_SIG_PARENT,
ConditionOpcode.AGG_SIG_PUZZLE,
ConditionOpcode.AGG_SIG_AMOUNT,
ConditionOpcode.AGG_SIG_PUZZLE_AMOUNT,
ConditionOpcode.AGG_SIG_PARENT_AMOUNT,
ConditionOpcode.AGG_SIG_PARENT_PUZZLE,
]:
ret[code] = std_hash(agg_sig_data + code)
arvidn marked this conversation as resolved.
Show resolved Hide resolved

ret[ConditionOpcode.AGG_SIG_ME] = agg_sig_data
return ret


def pkm_pairs(conditions: SpendBundleConditions, additional_data: bytes) -> Tuple[List[bytes48], List[bytes]]:
ret: Tuple[List[bytes48], List[bytes]] = ([], [])

data = agg_sig_additional_data(additional_data)

for pk, msg in conditions.agg_sig_unsafe:
ret[0].append(bytes48(pk))
ret[1].append(msg)
if msg.endswith(additional_data):
raise ConsensusError(Err.INVALID_CONDITION)
for disallowed in data.values():
if msg.endswith(disallowed):
raise ConsensusError(Err.INVALID_CONDITION)

for spend in conditions.spends:
richardkiss marked this conversation as resolved.
Show resolved Hide resolved
for pk, msg in spend.agg_sig_parent:
ret[0].append(bytes48(pk))
richardkiss marked this conversation as resolved.
Show resolved Hide resolved
ret[1].append(msg + spend.parent_id + data[ConditionOpcode.AGG_SIG_PARENT])

for pk, msg in spend.agg_sig_puzzle:
ret[0].append(bytes48(pk))
ret[1].append(msg + spend.puzzle_hash + data[ConditionOpcode.AGG_SIG_PUZZLE])

for pk, msg in spend.agg_sig_amount:
ret[0].append(bytes48(pk))
ret[1].append(msg + int_to_bytes(spend.coin_amount) + data[ConditionOpcode.AGG_SIG_AMOUNT])
richardkiss marked this conversation as resolved.
Show resolved Hide resolved

for pk, msg in spend.agg_sig_puzzle_amount:
ret[0].append(bytes48(pk))
ret[1].append(
msg + spend.puzzle_hash + int_to_bytes(spend.coin_amount) + data[ConditionOpcode.AGG_SIG_PUZZLE_AMOUNT]
)

for pk, msg in spend.agg_sig_parent_amount:
ret[0].append(bytes48(pk))
ret[1].append(
msg + spend.parent_id + int_to_bytes(spend.coin_amount) + data[ConditionOpcode.AGG_SIG_PARENT_AMOUNT]
)

for pk, msg in spend.agg_sig_parent_puzzle:
ret[0].append(bytes48(pk))
ret[1].append(msg + spend.parent_id + spend.puzzle_hash + data[ConditionOpcode.AGG_SIG_PARENT_PUZZLE])

for pk, msg in spend.agg_sig_me:
ret[0].append(bytes48(pk))
ret[1].append(msg + spend.coin_id + additional_data)
ret[1].append(msg + spend.coin_id + data[ConditionOpcode.AGG_SIG_ME])

return ret


def validate_cwa(cwa: ConditionWithArgs) -> None:
if (
len(cwa.vars) != 2
or len(cwa.vars[0]) != 48
or len(cwa.vars[1]) > 1024
or cwa.vars[0] is None
or cwa.vars[1] is None
):
raise ConsensusError(Err.INVALID_CONDITION)


def pkm_pairs_for_conditions_dict(
conditions_dict: Dict[ConditionOpcode, List[ConditionWithArgs]], coin_name: bytes32, additional_data: bytes
conditions_dict: Dict[ConditionOpcode, List[ConditionWithArgs]],
coin: Coin,
additional_data: bytes,
) -> List[Tuple[bytes48, bytes]]:
assert coin_name is not None
ret: List[Tuple[bytes48, bytes]] = []

data = agg_sig_additional_data(additional_data)

for cwa in conditions_dict.get(ConditionOpcode.AGG_SIG_UNSAFE, []):
assert len(cwa.vars) == 2
assert len(cwa.vars[0]) == 48 and len(cwa.vars[1]) <= 1024
assert cwa.vars[0] is not None and cwa.vars[1] is not None
if cwa.vars[1].endswith(additional_data):
raise ConsensusError(Err.INVALID_CONDITION)
validate_cwa(cwa)
for disallowed in data.values():
if cwa.vars[1].endswith(disallowed):
raise ConsensusError(Err.INVALID_CONDITION)
ret.append((bytes48(cwa.vars[0]), cwa.vars[1]))

for cwa in conditions_dict.get(ConditionOpcode.AGG_SIG_PARENT, []):
validate_cwa(cwa)
ret.append(
(
bytes48(cwa.vars[0]),
cwa.vars[1] + coin.parent_coin_info + data[ConditionOpcode.AGG_SIG_PARENT],
)
)

for cwa in conditions_dict.get(ConditionOpcode.AGG_SIG_PUZZLE, []):
validate_cwa(cwa)
ret.append((bytes48(cwa.vars[0]), cwa.vars[1] + coin.puzzle_hash + data[ConditionOpcode.AGG_SIG_PUZZLE]))

for cwa in conditions_dict.get(ConditionOpcode.AGG_SIG_AMOUNT, []):
validate_cwa(cwa)
ret.append(
(
bytes48(cwa.vars[0]),
cwa.vars[1] + int_to_bytes(coin.amount) + data[ConditionOpcode.AGG_SIG_AMOUNT],
)
)

for cwa in conditions_dict.get(ConditionOpcode.AGG_SIG_PUZZLE_AMOUNT, []):
validate_cwa(cwa)
ret.append(
(
bytes48(cwa.vars[0]),
cwa.vars[1]
+ coin.puzzle_hash
wallentx marked this conversation as resolved.
Show resolved Hide resolved
+ int_to_bytes(coin.amount)
+ data[ConditionOpcode.AGG_SIG_PUZZLE_AMOUNT],
)
)

for cwa in conditions_dict.get(ConditionOpcode.AGG_SIG_PARENT_AMOUNT, []):
validate_cwa(cwa)
ret.append(
(
bytes48(cwa.vars[0]),
cwa.vars[1]
+ coin.parent_coin_info
+ int_to_bytes(coin.amount)
+ data[ConditionOpcode.AGG_SIG_PARENT_AMOUNT],
)
)

for cwa in conditions_dict.get(ConditionOpcode.AGG_SIG_PARENT_PUZZLE, []):
validate_cwa(cwa)
ret.append(
(
bytes48(cwa.vars[0]),
cwa.vars[1] + coin.parent_coin_info + coin.puzzle_hash + data[ConditionOpcode.AGG_SIG_PARENT_PUZZLE],
)
)

for cwa in conditions_dict.get(ConditionOpcode.AGG_SIG_ME, []):
assert len(cwa.vars) == 2
assert len(cwa.vars[0]) == 48 and len(cwa.vars[1]) <= 1024
assert cwa.vars[0] is not None and cwa.vars[1] is not None
ret.append((bytes48(cwa.vars[0]), cwa.vars[1] + coin_name + additional_data))
validate_cwa(cwa)
ret.append((bytes48(cwa.vars[0]), cwa.vars[1] + coin.name() + data[ConditionOpcode.AGG_SIG_ME]))

return ret


Expand Down
2 changes: 1 addition & 1 deletion chia/wallet/cat_wallet/cat_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ async def sign(self, spend_bundle: SpendBundle) -> SpendBundle:
)
synthetic_pk = synthetic_secret_key.get_g1()
for pk, msg in pkm_pairs_for_conditions_dict(
conditions, spend.coin.name(), self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA
conditions, spend.coin, self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA
):
try:
assert bytes(synthetic_pk) == pk
Expand Down
2 changes: 1 addition & 1 deletion chia/wallet/did_wallet/did_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -1159,7 +1159,7 @@ async def sign(self, spend_bundle: SpendBundle) -> SpendBundle:
)
synthetic_pk = synthetic_secret_key.get_g1()
for pk, msg in pkm_pairs_for_conditions_dict(
conditions, spend.coin.name(), self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA
conditions, spend.coin, self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA
):
try:
assert bytes(synthetic_pk) == pk
Expand Down
2 changes: 1 addition & 1 deletion chia/wallet/nft_wallet/nft_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ async def sign(self, spend_bundle: SpendBundle, puzzle_hashes: Optional[List[byt
self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM,
)
for pk, msg in pkm_pairs_for_conditions_dict(
conditions, spend.coin.name(), self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA
conditions, spend.coin, self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA
):
try:
sk = pks.get(pk)
Expand Down
2 changes: 1 addition & 1 deletion chia/wallet/sign_coin_spends.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ async def sign_coin_spends(
# Get AGG_SIG conditions
conditions_dict = conditions_dict_for_solution(coin_spend.puzzle_reveal, coin_spend.solution, max_cost)
# Create signature
for pk_bytes, msg in pkm_pairs_for_conditions_dict(conditions_dict, coin_spend.coin.name(), additional_data):
for pk_bytes, msg in pkm_pairs_for_conditions_dict(conditions_dict, coin_spend.coin, additional_data):
pk = blspy.G1Element.from_bytes(pk_bytes)
pk_list.append(pk)
msg_list.append(msg)
Expand Down
2 changes: 1 addition & 1 deletion chia/wallet/util/debug_spend_bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def debug_spend_bundle(spend_bundle, agg_sig_additional_data=DEFAULT_CONSTANTS.A
continue

conditions = conditions_dict_for_solution(puzzle_reveal, solution, INFINITE_COST)
for pk_bytes, m in pkm_pairs_for_conditions_dict(conditions, coin_name, agg_sig_additional_data):
for pk_bytes, m in pkm_pairs_for_conditions_dict(conditions, coin, agg_sig_additional_data):
pks.append(G1Element.from_bytes(pk_bytes))
msgs.append(m)
print()
Expand Down
Loading