; This is an outer puzzle for a coin that imposes a restriction that the coin can only be spent with an
; announcement from a "verified credential" whose proofs are present due to a DID in a list of authorized providers.

; A "verified credential" is defined as the following puzzle stack:
;  - singleton top layer
;    |_exigent metadata layer
;      |_eml transfer program covenant adapter
;      | |_covenant layer
;      |   |_initial puzzle (hashed)
;      |   | |_singleton top layer
;      |   |   |_exigent metadata layer
;      |   |     |_(mod (_ _ (provider tp)) (list (c provider ()) tp ())) (guaranteed metadata to be nil)
;      |   |     |_p2 announced delegated puzzle
;      |   |_eml covenant morpher
;      |   |_eml update metadata with DID
;      |_<inner puzzle>
; The information to construct a VC is contained in a highly optimized way within CREDENTIAL_STRUCT
;
; This puzzle does many things:
;  - Tree hash of a partially revealed tree to come to a proof hash present in the accompanying VC singleton
;  - Validate the revealed proofs with a curried proofs checker
;  - Assert an announcement from a VC with a message of 0xca which signals this spend is ok
;  - Wrap all of its children in this layer
;  - Create an announcement in the 0xcd namespace of each output it is creating (for a VC to assert if it wishes)
;  - Block any announcements coming from the inner puzzle in the 0xcd namespace
(mod
  (
    ; We curry twice: first, all of the static information we need, then the hash of the program with all of that info
    ; this allows use to be more efficient when calculating our own hash (a (q . SELF_HASH) (c SELF_HASH (x INNER_PUZZLE 1)))
    ; curried first
    CREDENTIAL_STRUCT
    AUTHORIZED_PROVIDERS
    PROOFS_CHECKER
    ; curried second
    SELF_HASH
    INNER_PUZZLE
    proof_of_inclusions  ; Proof that key/value pairs exist in a tree (the root of the tree is implicit from this)
    proof_checker_solution  ; solution to PROOFS_CHECKER program
    provider_id  ; the DID that is curried into the metadata of the VC that is authorizing this coin
    credential_id  ; the launcher ID of the VC that is authorizing this coin
    credential_inner_puzhash  ; The <inner puzzle> (see above) of the VC that is authorizing this coin
    my_coin_id  ; This coin's ID
    inner_solution
  )

  (include curry.clib)
  (include condition_codes.clib)
  (include utility_macros.clib)

  ; take two lists and merge them into one
  (defun merge_list (list_a list_b)
    (if list_a
        (c (f list_a) (merge_list (r list_a) list_b))
        list_b
    )
  )

  (defconstant announcement_namespace 0xcd)
  (defconstant ONE 1)

  (defun-inline wrap_puzhash
    (
      SELF_HASH
      puzhash
    )

    (curry_hashes SELF_HASH
      (sha256 ONE SELF_HASH)
      puzhash
    )
  )

  ; Does three things:
  ; 1) Wraps create coins with this layer
  ; 2) Announces those create coins in a namespace
  ; 3) Raises on announcements from the inner puzzle that could possibly be in the namespace
  (defun process_conditions
    (
      SELF_HASH
      conditions
    )
    (if conditions
        (if (= (f (f conditions)) CREATE_COIN)
            (c
              (list
                CREATE_COIN_ANNOUNCEMENT
                (concat
                  announcement_namespace
                  (sha256 (f (r (f conditions))) (f (r (r (f conditions)))))
                )
              )
              (c
                (c
                  CREATE_COIN
                  (c
                    (wrap_puzhash SELF_HASH (f (r (f conditions))))
                    (r (r (f conditions)))
                  )
                )
                (process_conditions SELF_HASH (r conditions))
              )
            )
            ; else - not a create coin
            (assert (or
                (not (= (f (f conditions)) CREATE_COIN_ANNOUNCEMENT))  ; some coin announcements should be blocked
                (not (= 33 (strlen (f (r (f conditions))))))  ; we only care about blocking 0xcd + some_hash
                (not (= announcement_namespace (substr (f (r (f conditions))) 0 ONE)))  ; block prefix 0xcd
              )
              ; then
              (c
                (f conditions)
                (process_conditions SELF_HASH (r conditions))
              )
            )
        )
        ; else - end of list
        ()
    )
  )

  (defun create_vc_puzhash_stage_2
    (
      (
        (
          (
            SINGLETON_MOD_HASH
            .
            SINGLETON_LAUNCHER_HASH
          )
          .
          (
            OWNERSHIP_LAYER_MOD_HASH
            .
            ADAPTER_MOD_HASH
          )
        )
        .
        (
          INITIAL_SINGLETON_INNER_PUZHASH
          .
          (
            TWO_AND_COVENANT_MOD_HASH  ; (concat 2 COVENANT_MOD_HASH)
            .
            REST_COVENANT_ARGS_HASH  ; ((c PARENT_MORPHER (c EML_DID_TP 1)) . ()) hashed
          )
        )
      )
      provider_id
      credential_singleton_struct_hash
      proof_hash
      credential_inner_puzhash
      tp_hash
    )

    (curry_hashes SINGLETON_MOD_HASH
      credential_singleton_struct_hash
      (curry_hashes OWNERSHIP_LAYER_MOD_HASH
        (sha256 ONE OWNERSHIP_LAYER_MOD_HASH)
        (sha256 TWO
          (sha256 ONE provider_id)
          (sha256 ONE proof_hash)
        )
        tp_hash
        (sha256 ONE tp_hash)
        credential_inner_puzhash
      )
    )
  )

  (defun create_vc_puzhash
    (
      CREDENTIAL_STRUCT
      provider_id
      credential_singleton_struct_hash
      proof_hash
      credential_inner_puzhash
    )

    (create_vc_puzhash_stage_2
      CREDENTIAL_STRUCT
      provider_id
      credential_singleton_struct_hash
      proof_hash
      credential_inner_puzhash
      (curry_hashes (r (r (f CREDENTIAL_STRUCT)))  ; ADAPTER_MOD_HASH
        (sha256
          ; apply
          (two_sha256_one_a_kw)
          (sha256
            ; func
            (f (r (r CREDENTIAL_STRUCT)))  ; TWO_AND_COVENANT_MOD_HASH
            (sha256 TWO
              ; args
              (sha256
                (two_sha256_one_c_kw)
                (sha256 TWO
                  (sha256 TWO
                    (sha256_one_one)
                    (sha256 ONE (curry_hashes (f (f (f CREDENTIAL_STRUCT)))  ; SINGLETON_MOD_HASH
                        credential_singleton_struct_hash
                        (f (r CREDENTIAL_STRUCT))  ; INITIAL_SINGLETON_INNER_PUZHASH
                    ))
                  )
                  (r (r (r CREDENTIAL_STRUCT)))  ; REST_COVENANT_ARGS_HASH
                )
              )
              (sha256_one)
            )
          )
        )
      )
    )
  )

  ; utility function that turns the output of two calls to collapse_tree_and_note_leaf_info into a single return value
  (defun branch_hash_and_merge_info ((TREE1 PROOFS1) (TREE2 PROOFS2))
    (list
      (sha256 TWO TREE1 TREE2)
      (merge_list PROOFS1 PROOFS2)
    )
  )

  (defun collapse_tree_and_note_leaf_info (TREE PROOFS)
    (if (l TREE)
        (if (or (l (f TREE)) (l (r TREE)))  ; If either side is a cons, we have not reached a leaf pair yet
            (branch_hash_and_merge_info
              (collapse_tree_and_note_leaf_info (f TREE) ())
              ; we favor right because merge_list merges from left
              (collapse_tree_and_note_leaf_info (r TREE) PROOFS)
            )
            ; else - both first and rest are atoms, we are at a key/value pair
            (branch_hash_and_merge_info
              (list (sha256 ONE (f TREE)) ())
              (list (sha256 ONE (r TREE)) (c TREE PROOFS))
            )
        )
        (list TREE PROOFS)  ; All atoms that we reach must be pre-hashed subtrees
    )
  )

  (defun main
    (
      CREDENTIAL_STRUCT
      AUTHORIZED_PROVIDERS
      PROOFS_CHECKER
      SELF_HASH
      proof_checker_solution
      provider_id
      credential_id
      credential_inner_puzhash
      my_coin_id
      conditions
      (tree_hash proofs)
    )

    (assert
      ; Run proof checker to make sure it doesn't return () (fail)
      (a PROOFS_CHECKER (list proofs proof_checker_solution))
      (in provider_id AUTHORIZED_PROVIDERS)  ; VC needs to be in authorized list
      ; then
      (c
        (list ASSERT_MY_COIN_ID my_coin_id)
        (c
          (list ASSERT_PUZZLE_ANNOUNCEMENT
            (sha256
              (create_vc_puzhash
                CREDENTIAL_STRUCT
                provider_id
                (sha256 TWO
                  (sha256 ONE (f (f (f CREDENTIAL_STRUCT))))  ; SINGLETON_MOD_HASH
                  (sha256 TWO
                    (sha256 ONE credential_id)
                    (sha256 ONE (r (f (f CREDENTIAL_STRUCT))))  ; SINGLETON_LAUNCHER_HASH
                  )
                )
                tree_hash
                credential_inner_puzhash
              )
              (sha256 my_coin_id 0xca)  ; my_coin_id because VC must announce specifically to us
            )
          )
          (process_conditions
            SELF_HASH
            conditions
          )
        )
      )
    )
  )

  (main
    CREDENTIAL_STRUCT
    AUTHORIZED_PROVIDERS
    PROOFS_CHECKER
    SELF_HASH
    proof_checker_solution
    provider_id
    credential_id
    credential_inner_puzhash
    my_coin_id
    (a INNER_PUZZLE inner_solution)
    (collapse_tree_and_note_leaf_info proof_of_inclusions ())
  )
)
