mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2024-11-10 12:29:49 +03:00
c1c4f99f6c
* First crack at VC puzzles * Add p2_puzzle_or_hidden_puzzle * First crack at drivers * Basic test infra * compile clsp and test covenant layer * lint * Add test for match_covenant_layer * Add tests for DID TP * Add tests for DID backdoor * Add tests for p2_puzzle_or_hidden_puzzle * Change DID backdoor -> p2_puzzle_w_auth + did_puzzle_authorizer * Add a VerifiedCredential class * Make p2_puz_or_hidden_puz viral * Add morpher solution to covenant layer * Add capability to update the proofs of a VC * update_proofs -> do_spend * Finish lifecycle of VC * Make backdoor puzzle, remove p2_puzzle_w_auth from stack * Extract p2_puzzle_w_auth to its own files * lint * rework launch process so it works after revocation * Add some extra tests for the final state after revocation * Add cost logging * slight opimizations * python comments and ergonomic improvements * Comments for chialisp * Add VC wallet and store infra * Add a negative test for updating proofs w/o DID * First crack at CR CAT * Turn two hashes into one * Extract some shatrees out of puzzle hash creation * Add a stager function * Precalculate initial_singleton_inner_puzhash_hash * Add another stager * Refine stagers * Add cost logging and assert provider is authorized * Add sketched out CRCAT class * black * Test most of CRCAT drivers * lint * Remove an unnecessary param * Don't commit to singleton ID in parent morpher * Extract DID to ownership layer * lint * use intitial puzzle hash for covenant as proof to morpher * Collapse hash of parent morhper * Remove stagers * Rename some params * Use SELF_HASH construct * remove dependency on shatree * Minor optimization of curried hashes * Balance tree * hint -> remark * Make a v2 ownership layer for cost-sake * (WIP) Migrate to tp hash at ownership layer * (WIP) Return tp_hash from tp * use a stager again * use constants for ints * use more constants * inline a function, get rid of constant * Switch to tree metadata on ownership layer * Impelement wallet launch vc * Minor fixes and rename a bunch of stuff * lint * Fix tests * First crack at proof checking * Remove unused imports * Merge tests to get better coverageg * Fix is_vc methods * pre-commit * pre-commit again * Implement VC spend * make sql pre-commit check work with git worktrees * CRCATWallet * Comment reasoning in script file Co-authored-by: Kyle Altendorf <sda@fstab.net> * Add a concept of VCProofs * Show proofs in vc_get_vc_list as well * Implememnt CRCAT wallet * Extend lineage store * Make tests pass * WalletIdentifier fix * Add revocation RPC * forgotten bytes32 * Return coin name with vc record * First crack at adding VC authorizations to offers * Add CROuterPuzzle * Fix wallet_funcs.py * VCWallet changes only * Fix wallet db schema test * Add RPC client methods and tests * Allow a path for user to clear VC * Mint VC command * Get VCs command * Forgotten hex compilation * Automatically fetch DID when we have it * Update Proofs command * Add Proof Reveal command * Get Proofs For Root command * Revoke VC command * Chialisp pre-commit cleanup * Deprecate as_python() * Add a limit to some sql queries * Fix some bad imports * Fix changed wallet sync function * Check for tail condition amounts in CAT spending * Add RPC docs * only need one mojo for a singleton * Fix comment * remove create_puzhash from add_new_wallet * Remove create_tandam_xch_tx * fix wallet_state_manager hinting * Rename RPCs * Use streamable classes for RPC request parsing * add back accidentally deleted method call * Remove unnecessary copy() call * Add negative test for using wrong DID to update proofs/revoke vc * Add negative test for making sure invalid proofs can't be proven * Add negative test for trying to piggyback on top an unrelated VC spend * Make output_amount a uint64 * some chialisp bug fixes * chialisp readability improvements * Force same provider on provider update of DID * typo in EML * Add vc_wallet folders to package list * mypy * Increase test coverage a bit * Add a bit more coverage * rework wallet creation * Delete is_addition_relevant * Coveralls ignores * remove brick reference * coveralls-ignore --> pragma: no cover * Couple more ignores * typo corrections --------- Co-authored-by: ytx1991 <t.yu@chia.net> Co-authored-by: Kyle Altendorf <sda@fstab.net>
604 lines
23 KiB
Python
604 lines
23 KiB
Python
from __future__ import annotations
|
|
|
|
import functools
|
|
from dataclasses import dataclass, replace
|
|
from typing import Iterable, List, Optional, Tuple, Type, TypeVar
|
|
|
|
from clvm.casts import int_to_bytes
|
|
|
|
from chia.types.blockchain_format.coin import Coin, coin_as_list
|
|
from chia.types.blockchain_format.program import Program
|
|
from chia.types.blockchain_format.sized_bytes import bytes32
|
|
from chia.types.coin_spend import CoinSpend
|
|
from chia.util.hash import std_hash
|
|
from chia.util.ints import uint64
|
|
from chia.wallet.cat_wallet.cat_utils import construct_cat_puzzle
|
|
from chia.wallet.lineage_proof import LineageProof
|
|
from chia.wallet.payment import Payment
|
|
from chia.wallet.puzzles.cat_loader import CAT_MOD
|
|
from chia.wallet.puzzles.load_clvm import load_clvm_maybe_recompile
|
|
from chia.wallet.puzzles.singleton_top_layer_v1_1 import SINGLETON_LAUNCHER_HASH, SINGLETON_MOD_HASH
|
|
from chia.wallet.uncurried_puzzle import UncurriedPuzzle, uncurry_puzzle
|
|
from chia.wallet.vc_wallet.vc_drivers import (
|
|
COVENANT_LAYER_HASH,
|
|
EML_TP_COVENANT_ADAPTER_HASH,
|
|
EXTIGENT_METADATA_LAYER_HASH,
|
|
GUARANTEED_NIL_TP,
|
|
P2_ANNOUNCED_DELEGATED_PUZZLE,
|
|
create_did_tp,
|
|
create_eml_covenant_morpher,
|
|
)
|
|
|
|
# Mods
|
|
CREDENTIAL_RESTRICTION: Program = load_clvm_maybe_recompile(
|
|
"credential_restriction.clsp",
|
|
package_or_requirement="chia.wallet.vc_wallet.cr_puzzles",
|
|
include_standard_libraries=True,
|
|
)
|
|
CREDENTIAL_RESTRICTION_HASH: bytes32 = CREDENTIAL_RESTRICTION.get_tree_hash()
|
|
PROOF_FLAGS_CHECKER: Program = load_clvm_maybe_recompile(
|
|
"flag_proofs_checker.clsp",
|
|
package_or_requirement="chia.wallet.vc_wallet.cr_puzzles",
|
|
include_standard_libraries=True,
|
|
)
|
|
|
|
|
|
# Basic drivers
|
|
def construct_cr_layer(
|
|
authorized_providers: List[bytes32],
|
|
proofs_checker: Program,
|
|
inner_puzzle: Program,
|
|
) -> Program:
|
|
first_curry: Program = CREDENTIAL_RESTRICTION.curry(
|
|
Program.to(
|
|
(
|
|
(
|
|
(
|
|
SINGLETON_MOD_HASH,
|
|
SINGLETON_LAUNCHER_HASH,
|
|
),
|
|
(
|
|
EXTIGENT_METADATA_LAYER_HASH,
|
|
EML_TP_COVENANT_ADAPTER_HASH,
|
|
),
|
|
),
|
|
(
|
|
Program.to(EXTIGENT_METADATA_LAYER_HASH)
|
|
.curry(
|
|
Program.to(EXTIGENT_METADATA_LAYER_HASH).get_tree_hash(),
|
|
Program.to(None),
|
|
GUARANTEED_NIL_TP,
|
|
GUARANTEED_NIL_TP.get_tree_hash(),
|
|
P2_ANNOUNCED_DELEGATED_PUZZLE,
|
|
)
|
|
.get_tree_hash_precalc(
|
|
EXTIGENT_METADATA_LAYER_HASH, Program.to(EXTIGENT_METADATA_LAYER_HASH).get_tree_hash()
|
|
),
|
|
(
|
|
Program.to(
|
|
int_to_bytes(2)
|
|
+ Program.to((1, COVENANT_LAYER_HASH)).get_tree_hash_precalc(COVENANT_LAYER_HASH)
|
|
),
|
|
Program.to(
|
|
(
|
|
[
|
|
4,
|
|
(1, create_eml_covenant_morpher(create_did_tp().get_tree_hash())),
|
|
[4, (1, create_did_tp()), 1],
|
|
],
|
|
None,
|
|
)
|
|
).get_tree_hash(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
authorized_providers,
|
|
proofs_checker,
|
|
)
|
|
return first_curry.curry(first_curry.get_tree_hash(), inner_puzzle)
|
|
|
|
|
|
# Coverage coming with CR-CAT Wallet
|
|
def match_cr_layer(
|
|
uncurried_puzzle: UncurriedPuzzle,
|
|
) -> Optional[Tuple[List[bytes32], Program, Program]]: # pragma: no cover
|
|
if uncurried_puzzle.mod == CREDENTIAL_RESTRICTION:
|
|
extra_uncurried_puzzle = uncurry_puzzle(uncurried_puzzle.mod)
|
|
return (
|
|
[bytes32(provider.atom) for provider in extra_uncurried_puzzle.args.at("rf").as_iter()],
|
|
extra_uncurried_puzzle.args.at("rrf"),
|
|
uncurried_puzzle.args.at("rf"),
|
|
)
|
|
else:
|
|
return None
|
|
|
|
|
|
def solve_cr_layer(
|
|
proof_of_inclusions: Program,
|
|
proof_checker_solution: Program,
|
|
provider_id: bytes32,
|
|
vc_launcher_id: bytes32,
|
|
vc_inner_puzhash: bytes32,
|
|
my_coin_id: bytes32,
|
|
inner_solution: Program,
|
|
) -> Program:
|
|
solution: Program = Program.to(
|
|
[
|
|
proof_of_inclusions,
|
|
proof_checker_solution,
|
|
provider_id,
|
|
vc_launcher_id,
|
|
vc_inner_puzhash,
|
|
my_coin_id,
|
|
inner_solution,
|
|
]
|
|
)
|
|
return solution
|
|
|
|
|
|
_T_CRCAT = TypeVar("_T_CRCAT", bound="CRCAT")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CRCAT:
|
|
coin: Coin
|
|
tail_hash: bytes32
|
|
lineage_proof: LineageProof
|
|
authorized_providers: List[bytes32]
|
|
proofs_checker: Program
|
|
inner_puzzle_hash: bytes32
|
|
|
|
@classmethod
|
|
def launch(
|
|
cls: Type[_T_CRCAT],
|
|
# General CAT launching info
|
|
origin_coin: Coin,
|
|
payment: Payment,
|
|
tail: Program,
|
|
tail_solution: Program,
|
|
# CR Layer params
|
|
authorized_providers: List[bytes32],
|
|
proofs_checker: Program,
|
|
# Probably never need this but some tail might
|
|
optional_lineage_proof: Optional[LineageProof] = None,
|
|
) -> Tuple[Program, CoinSpend, CRCAT]:
|
|
"""
|
|
Launch a new CR-CAT from XCH.
|
|
|
|
Returns a delegated puzzle to run that creates the eve CAT, an eve coin spend of the CAT, and the expected class
|
|
representation after all relevant coin spends have been confirmed on chain.
|
|
"""
|
|
tail_hash: bytes32 = tail.get_tree_hash()
|
|
|
|
new_cr_layer_hash: bytes32 = construct_cr_layer(
|
|
authorized_providers,
|
|
proofs_checker,
|
|
payment.puzzle_hash, # type: ignore
|
|
).get_tree_hash_precalc(payment.puzzle_hash)
|
|
new_cat_puzhash: bytes32 = construct_cat_puzzle(
|
|
CAT_MOD,
|
|
tail_hash,
|
|
new_cr_layer_hash, # type: ignore
|
|
).get_tree_hash_precalc(new_cr_layer_hash)
|
|
|
|
eve_innerpuz: Program = Program.to(
|
|
(
|
|
1,
|
|
[
|
|
[51, new_cr_layer_hash, payment.amount, payment.memos],
|
|
[51, None, -113, tail, tail_solution],
|
|
[60, None],
|
|
[1, payment.puzzle_hash, authorized_providers, proofs_checker],
|
|
],
|
|
)
|
|
)
|
|
eve_cat_puzzle: Program = construct_cat_puzzle(
|
|
CAT_MOD,
|
|
tail_hash,
|
|
eve_innerpuz,
|
|
)
|
|
eve_cat_puzzle_hash: bytes32 = eve_cat_puzzle.get_tree_hash()
|
|
|
|
eve_coin: Coin = Coin(origin_coin.name(), eve_cat_puzzle_hash, payment.amount)
|
|
dpuz: Program = Program.to(
|
|
(
|
|
1,
|
|
[
|
|
[51, eve_cat_puzzle_hash, payment.amount],
|
|
[61, std_hash(eve_coin.name())],
|
|
],
|
|
)
|
|
)
|
|
|
|
eve_proof: LineageProof = LineageProof(
|
|
eve_coin.parent_coin_info,
|
|
eve_innerpuz.get_tree_hash(),
|
|
uint64(eve_coin.amount),
|
|
)
|
|
|
|
return (
|
|
dpuz,
|
|
CoinSpend(
|
|
eve_coin,
|
|
eve_cat_puzzle,
|
|
Program.to( # solve_cat
|
|
[
|
|
None,
|
|
optional_lineage_proof,
|
|
eve_coin.name(),
|
|
coin_as_list(eve_coin),
|
|
eve_proof.to_program(),
|
|
0,
|
|
0,
|
|
]
|
|
),
|
|
),
|
|
CRCAT(
|
|
Coin(eve_coin.name(), new_cat_puzhash, payment.amount),
|
|
tail_hash,
|
|
eve_proof,
|
|
authorized_providers,
|
|
proofs_checker,
|
|
payment.puzzle_hash,
|
|
),
|
|
)
|
|
|
|
def construct_puzzle(self, inner_puzzle: Program) -> Program:
|
|
return construct_cat_puzzle(
|
|
CAT_MOD,
|
|
self.tail_hash,
|
|
self.construct_cr_layer(inner_puzzle),
|
|
)
|
|
|
|
def construct_cr_layer(self, inner_puzzle: Program) -> Program:
|
|
return construct_cr_layer(
|
|
self.authorized_providers,
|
|
self.proofs_checker,
|
|
inner_puzzle,
|
|
)
|
|
|
|
@staticmethod
|
|
def is_cr_cat(puzzle_reveal: UncurriedPuzzle) -> Tuple[bool, str]:
|
|
"""
|
|
This takes an (uncurried) puzzle reveal and returns a boolean for whether the puzzle is a CR-CAT and an error
|
|
message for if the puzzle is a mismatch.
|
|
"""
|
|
if puzzle_reveal.mod != CAT_MOD:
|
|
return False, "top most layer is not a CAT" # pragma: no cover
|
|
layer_below_cat: UncurriedPuzzle = uncurry_puzzle(puzzle_reveal.args.at("rrf"))
|
|
if layer_below_cat.mod != CREDENTIAL_RESTRICTION:
|
|
return False, "CAT is not credential restricted" # pragma: no cover
|
|
|
|
# Coverage coming with CR-CAT Wallet
|
|
return True, "" # pragma: no cover
|
|
|
|
# Coverage coming with CR-CAT Wallet
|
|
@staticmethod
|
|
def get_inner_puzzle(puzzle_reveal: UncurriedPuzzle) -> Program: # pragma: no cover
|
|
return uncurry_puzzle(puzzle_reveal.args.at("rrf")).args.at("rf")
|
|
|
|
@staticmethod
|
|
def get_inner_solution(solution: Program) -> Program: # pragma: no cover
|
|
return solution.at("f").at("rrrrrrf")
|
|
|
|
@classmethod
|
|
def get_current_from_coin_spend(cls: Type[_T_CRCAT], spend: CoinSpend) -> CRCAT: # pragma: no cover
|
|
uncurried_puzzle: UncurriedPuzzle = uncurry_puzzle(spend.puzzle_reveal.to_program())
|
|
first_uncurried_cr_layer: UncurriedPuzzle = uncurry_puzzle(uncurried_puzzle.args.at("rrf"))
|
|
second_uncurried_cr_layer: UncurriedPuzzle = uncurry_puzzle(first_uncurried_cr_layer.mod)
|
|
return CRCAT(
|
|
spend.coin,
|
|
bytes32(uncurried_puzzle.args.at("rf").atom),
|
|
spend.solution.to_program().at("rf"),
|
|
[bytes32(ap.atom) for ap in second_uncurried_cr_layer.args.at("rf").as_iter()],
|
|
second_uncurried_cr_layer.args.at("rrf"),
|
|
first_uncurried_cr_layer.args.at("f").get_tree_hash(),
|
|
)
|
|
|
|
@classmethod
|
|
def get_next_from_coin_spend(
|
|
cls: Type[_T_CRCAT],
|
|
parent_spend: CoinSpend,
|
|
conditions: Optional[Program] = None, # For optimization purposes, the conditions may already have been run
|
|
) -> List[CRCAT]:
|
|
"""
|
|
Given a coin spend, this will return the next CR-CATs that were created as an output of that spend.
|
|
Inner puzzle output conditions may also be supplied as an optimization.
|
|
|
|
This is the main method to use when syncing. It can also sync from a CAT spend that was not a CR-CAT so long
|
|
as the spend output a remark condition that was (REMARK authorized_providers proofs_checker)
|
|
"""
|
|
coin_name: bytes32 = parent_spend.coin.name()
|
|
puzzle: Program = parent_spend.puzzle_reveal.to_program()
|
|
solution: Program = parent_spend.solution.to_program()
|
|
|
|
# Get info by uncurrying
|
|
_, tail_hash_as_prog, potential_cr_layer = puzzle.uncurry()[1].as_iter()
|
|
new_inner_puzzle_hash: Optional[bytes32] = None
|
|
if potential_cr_layer.uncurry()[0].uncurry()[0] != CREDENTIAL_RESTRICTION:
|
|
# If the previous spend is not a CR-CAT:
|
|
# we look for a remark condition that tells us the authorized_providers and proofs_checker
|
|
inner_solution: Program = solution.at("f")
|
|
if conditions is None:
|
|
conditions = potential_cr_layer.run(inner_solution)
|
|
for condition in conditions.as_iter():
|
|
if condition.at("f") == Program.to(1):
|
|
new_inner_puzzle_hash = bytes32(condition.at("rf").atom)
|
|
authorized_providers_as_prog: Program = condition.at("rrf")
|
|
proofs_checker: Program = condition.at("rrrf")
|
|
break
|
|
else:
|
|
raise ValueError(
|
|
"Previous spend was not a CR-CAT, nor did it properly remark the CR params"
|
|
) # pragma: no cover
|
|
lineage_inner_puzhash: bytes32 = potential_cr_layer.get_tree_hash()
|
|
else:
|
|
# Otherwise the info we need will be in the puzzle reveal
|
|
cr_first_curry, self_hash_and_innerpuz = potential_cr_layer.uncurry()
|
|
_, authorized_providers_as_prog, proofs_checker = cr_first_curry.uncurry()[1].as_iter()
|
|
_, inner_puzzle = self_hash_and_innerpuz.as_iter()
|
|
inner_solution = solution.at("f").at("rrrrrrf")
|
|
if conditions is None:
|
|
conditions = inner_puzzle.run(inner_solution)
|
|
inner_puzzle_hash: bytes32 = inner_puzzle.get_tree_hash()
|
|
lineage_inner_puzhash = construct_cr_layer(
|
|
authorized_providers_as_prog,
|
|
proofs_checker,
|
|
inner_puzzle_hash, # type: ignore
|
|
).get_tree_hash_precalc(inner_puzzle_hash)
|
|
|
|
# Convert all of the old stuff into python
|
|
authorized_providers: List[bytes32] = [bytes32(p.atom) for p in authorized_providers_as_prog.as_iter()]
|
|
new_lineage_proof: LineageProof = LineageProof(
|
|
parent_spend.coin.parent_coin_info,
|
|
lineage_inner_puzhash,
|
|
uint64(parent_spend.coin.amount),
|
|
)
|
|
|
|
# Almost complete except the coin's full puzzle hash which we want to use the class method to calculate
|
|
partially_completed_crcats: List[CRCAT] = [
|
|
CRCAT(
|
|
Coin(coin_name, bytes(32), uint64(condition.at("rrf").as_int())),
|
|
bytes32(tail_hash_as_prog.atom),
|
|
new_lineage_proof,
|
|
authorized_providers,
|
|
proofs_checker,
|
|
bytes32(condition.at("rf").atom) if new_inner_puzzle_hash is None else new_inner_puzzle_hash,
|
|
)
|
|
for condition in conditions.as_iter()
|
|
if condition.at("f").as_int() == 51 and condition.at("rrf") != Program.to(-113)
|
|
]
|
|
|
|
return [
|
|
replace(
|
|
crcat,
|
|
coin=Coin(
|
|
crcat.coin.parent_coin_info,
|
|
crcat.construct_puzzle(crcat.inner_puzzle_hash).get_tree_hash_precalc( # type: ignore
|
|
crcat.inner_puzzle_hash
|
|
),
|
|
crcat.coin.amount,
|
|
),
|
|
)
|
|
for crcat in partially_completed_crcats
|
|
]
|
|
|
|
def do_spend(
|
|
self,
|
|
# CAT solving info
|
|
previous_coin_id: bytes32,
|
|
next_coin_proof: LineageProof,
|
|
previous_subtotal: int,
|
|
extra_delta: int,
|
|
# CR layer solving info
|
|
proof_of_inclusions: Program,
|
|
proof_checker_solution: Program,
|
|
provider_id: bytes32,
|
|
vc_launcher_id: bytes32,
|
|
vc_inner_puzhash: bytes32,
|
|
# Inner puzzle and solution
|
|
inner_puzzle: Program,
|
|
inner_solution: Program,
|
|
# For optimization purposes the conditions may already have been run
|
|
conditions: Optional[Iterable[Program]] = None,
|
|
) -> Tuple[List[bytes32], CoinSpend, List["CRCAT"]]:
|
|
"""
|
|
Spend a CR-CAT.
|
|
|
|
Must give the CAT accounting information, the valid VC proof, and the inner puzzle and solution. The function
|
|
will return the announcement IDs for the VC to optionally assert, the spend of this CAT, and the class
|
|
representations of any CR-CAT outputs.
|
|
|
|
Likely, spend_many is more useful.
|
|
"""
|
|
# Gather the output information
|
|
announcement_ids: List[bytes32] = []
|
|
new_inner_puzzle_hashes_and_amounts: List[Tuple[bytes32, uint64]] = []
|
|
if conditions is None:
|
|
conditions = inner_puzzle.run(inner_solution).as_iter() # pragma: no cover
|
|
assert conditions is not None
|
|
for condition in conditions:
|
|
if condition.at("f").as_int() == 51 and condition.at("rrf").as_int() != -113:
|
|
new_inner_puzzle_hash: bytes32 = bytes32(condition.at("rf").atom)
|
|
new_amount: uint64 = uint64(condition.at("rrf").as_int())
|
|
announcement_ids.append(
|
|
std_hash(self.coin.name() + b"\xcd" + std_hash(new_inner_puzzle_hash + int_to_bytes(new_amount)))
|
|
)
|
|
new_inner_puzzle_hashes_and_amounts.append((new_inner_puzzle_hash, new_amount))
|
|
|
|
return (
|
|
announcement_ids,
|
|
CoinSpend(
|
|
self.coin,
|
|
self.construct_puzzle(inner_puzzle),
|
|
Program.to( # solve_cat
|
|
[
|
|
solve_cr_layer(
|
|
proof_of_inclusions,
|
|
proof_checker_solution,
|
|
provider_id,
|
|
vc_launcher_id,
|
|
vc_inner_puzhash,
|
|
self.coin.name(),
|
|
inner_solution,
|
|
),
|
|
self.lineage_proof.to_program(),
|
|
previous_coin_id,
|
|
coin_as_list(self.coin),
|
|
next_coin_proof.to_program(),
|
|
previous_subtotal,
|
|
extra_delta,
|
|
]
|
|
),
|
|
),
|
|
[
|
|
CRCAT(
|
|
Coin(
|
|
self.coin.name(),
|
|
self.construct_puzzle(new_inner_puzzle_hash).get_tree_hash_precalc( # type: ignore
|
|
new_inner_puzzle_hash
|
|
),
|
|
new_amount,
|
|
),
|
|
self.tail_hash,
|
|
LineageProof(
|
|
self.coin.parent_coin_info,
|
|
self.construct_cr_layer(self.inner_puzzle_hash).get_tree_hash_precalc( # type: ignore
|
|
self.inner_puzzle_hash
|
|
),
|
|
uint64(self.coin.amount),
|
|
),
|
|
self.authorized_providers,
|
|
self.proofs_checker,
|
|
new_inner_puzzle_hash,
|
|
)
|
|
for new_inner_puzzle_hash, new_amount in new_inner_puzzle_hashes_and_amounts
|
|
],
|
|
)
|
|
|
|
@classmethod
|
|
def spend_many(
|
|
cls: Type[_T_CRCAT],
|
|
inner_spends: List[Tuple[_T_CRCAT, Program, Program]], # CRCAT, inner puzzle, inner solution
|
|
# CR layer solving info
|
|
proof_of_inclusions: Program,
|
|
proof_checker_solution: Program,
|
|
provider_id: bytes32,
|
|
vc_launcher_id: bytes32,
|
|
vc_inner_puzhash: bytes32,
|
|
) -> Tuple[List[bytes32], List[CoinSpend], List[CRCAT]]:
|
|
"""
|
|
Spend a multiple CR-CATs.
|
|
|
|
This class will handle all of the CAT accounting information, the only necessary information is the inner
|
|
puzzle/solution, and the proof of a valid VC being spent along side all of the coins. There is currently no
|
|
support for multiple VCs being used across the spend. There is also currently no support for minting/melting.
|
|
"""
|
|
|
|
def next_index(index: int) -> int:
|
|
return 0 if index == len(inner_spends) - 1 else index + 1
|
|
|
|
def prev_index(index: int) -> int:
|
|
return index - 1
|
|
|
|
sorted_inner_spends: List[Tuple[_T_CRCAT, Program, Program]] = sorted(
|
|
inner_spends,
|
|
key=lambda spend: spend[0].coin.name(),
|
|
)
|
|
|
|
all_expected_announcements: List[bytes32] = []
|
|
all_coin_spends: List[CoinSpend] = []
|
|
all_new_crcats: List[CRCAT] = []
|
|
|
|
subtotal: int = 0
|
|
for i, inner_spend in enumerate(sorted_inner_spends):
|
|
crcat, inner_puzzle, inner_solution = inner_spend
|
|
conditions: List[Program] = list(inner_puzzle.run(inner_solution).as_iter())
|
|
output_amount: uint64 = uint64(
|
|
sum(
|
|
c.at("rrf").as_int()
|
|
for c in conditions
|
|
if c.at("f").as_int() == 51 and c.at("rrf").as_int() != -113
|
|
)
|
|
)
|
|
next_crcat, _, _ = sorted_inner_spends[next_index(i)]
|
|
prev_crcat, _, _ = sorted_inner_spends[prev_index(i)]
|
|
expected_announcements, coin_spend, new_crcats = crcat.do_spend(
|
|
prev_crcat.coin.name(),
|
|
LineageProof(
|
|
next_crcat.coin.parent_coin_info,
|
|
next_crcat.construct_cr_layer(
|
|
next_crcat.inner_puzzle_hash, # type: ignore
|
|
).get_tree_hash_precalc(next_crcat.inner_puzzle_hash),
|
|
uint64(next_crcat.coin.amount),
|
|
),
|
|
subtotal,
|
|
0, # TODO: add support for mint/melt
|
|
proof_of_inclusions,
|
|
proof_checker_solution,
|
|
provider_id,
|
|
vc_launcher_id,
|
|
vc_inner_puzhash,
|
|
inner_puzzle,
|
|
inner_solution,
|
|
conditions=conditions,
|
|
)
|
|
all_expected_announcements.extend(expected_announcements)
|
|
all_coin_spends.append(coin_spend)
|
|
all_new_crcats.extend(new_crcats)
|
|
|
|
subtotal = subtotal + crcat.coin.amount - output_amount
|
|
|
|
return all_expected_announcements, all_coin_spends, all_new_crcats
|
|
|
|
def expected_announcement(self) -> bytes32:
|
|
"""
|
|
The announcement a VC must make to this CAT in order to spend it
|
|
"""
|
|
return std_hash(self.coin.name() + b"\xca")
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CRCATSpend:
|
|
crcat: CRCAT
|
|
inner_puzzle: Program
|
|
inner_solution: Program
|
|
children: List[CRCAT]
|
|
provider_specified: bool
|
|
inner_conditions: List[Program]
|
|
|
|
# Coverage coming with CR-CAT wallet
|
|
@classmethod
|
|
def from_coin_spend(cls, spend: CoinSpend) -> CRCATSpend: # pragma: no cover
|
|
inner_puzzle: Program = CRCAT.get_inner_puzzle(uncurry_puzzle(spend.puzzle_reveal.to_program()))
|
|
inner_solution: Program = CRCAT.get_inner_solution(spend.solution.to_program())
|
|
inner_conditions: Program = inner_puzzle.run(inner_solution)
|
|
return cls(
|
|
CRCAT.get_current_from_coin_spend(spend),
|
|
inner_puzzle,
|
|
inner_solution,
|
|
CRCAT.get_next_from_coin_spend(spend, conditions=inner_conditions),
|
|
spend.solution.to_program().at("f").at("rrrrf") == Program.to(None),
|
|
list(inner_conditions.as_iter()),
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ProofsChecker:
|
|
flags: List[str]
|
|
|
|
def as_program(self) -> Program:
|
|
def byte_sort_flags(f1: str, f2: str) -> int:
|
|
return 1 if Program.to([10, (1, f1), (1, f2)]).run([]) == Program.to(None) else -1
|
|
|
|
return PROOF_FLAGS_CHECKER.curry(
|
|
[
|
|
Program.to((flag, 1))
|
|
for flag in sorted(
|
|
self.flags,
|
|
key=functools.cmp_to_key(byte_sort_flags),
|
|
)
|
|
]
|
|
)
|