kadena-io / pact

The Pact Smart Contract Language
https://docs.kadena.io/build/pact
BSD 3-Clause "New" or "Revised" License
583 stars 101 forks source link

SPV Endorsements, Yield/Resume #443

Closed sirlensalot closed 5 years ago

sirlensalot commented 5 years ago

UPDATE: this is now the discussion for automated SPV in defpacts via yield, see below: https://github.com/kadena-io/pact/issues/443#issuecomment-480469325


Burn/creates in chainweb use SPV to assure the create side, here's draft coin contract code:

 (defun delete-coin (delete-account create-chain-id create-account create-account-guard quantity)
    (with-capability (TRANSFER)
      (debit delete-account quantity)
      { "create-chain-id": create-chain-id
      , "create-account": create-account
      ... }))

  (defun create-coin (proof)
    (let ((outputs (at "outputs" (verify-spv "TXOUT" proof))))
      (enforce (= 1 (length outputs)) "only one tx in outputs")
      (bind (at 0 outputs)
        { "create-chain-id":= create-chain-id
        , "create-account" := create-account
        ...} ...)))

However, there is no assurance that the result object obtained from verify-spv was actually emitted by delete-coin on the other chain. Somehow we need to prove that the coin contract emitted this.

Proposal is to offer an (endorse-spv type output) operation to wrap results returned from some verifiable operation. The Pact runtime will store a special result value built from the hash of (TYPE,OUTPUT,calling-module-hash); this value is then encoded in some special endorsement field of the SPV-verified output object (e.g., in chainweb's TransactionOutput somewhere). It returns OUTPUT in order for that to be the recorded response to the call, to be recovered from the SPV output.

On the recipient side, (verify-spv type proof)'s backend will read the deserialized transaction output and enforce that endorsement is populated. If endorsement is populated, compute the hash of (TYPE,output,calling-module-hash) and enforce that it equals endorsement.

Design considerations:

EDIT: going with the TAG approach above, which has nice legibility properties and further specializes the hash, but keeping the explicit operation in tail position.

sirlensalot commented 5 years ago

Some more context is: the actual endorsement here is that the two chains have the same version of the smart contract, as reflected by the module hash. Thus a smart contract on chain B can trustlessly validate that some smart contract on chain A has the same module hash.

Most of the time this is a reasonable assumption, and especially with a non-forked coin contract. However, since SPV happens over a span of transactions, it is well possible that a newer contract is verifying an older one's endorsement, which can be handled with the module-hash blessing system such that blessed versions will also be honored (that is, tried when the current module hash fails).

The inverse situation, verifying a newer endorsement, might require the newer endorsing version to specifically target a past module hash for compatibility with known deployments. It could even be argued that the oldest blessed hash should be used for endorsement unless otherwise specified, with a specific hash specification targeting a minimum compatible version (after all, spv interactions might become incompatible on some upgrade).

sirlensalot commented 5 years ago

Example updated for proposal:

 (defun delete-coin (delete-account create-chain-id create-account create-account-guard quantity)
    (with-capability (TRANSFER)
      (debit delete-account quantity)
        (endorse-spv "TXOUT" 
          { "create-chain-id": create-chain-id
          , "create-account": create-account
      ... })))

  (defun create-coin (proof)
    (let ((outputs (at "outputs" (verify-spv "TXOUT" proof))))
      (enforce (= 1 (length outputs)) "only one tx in outputs")
      (bind (at 0 outputs)
        { "create-chain-id":= create-chain-id
        , "create-account" := create-account
        ...} ...)))
sirlensalot commented 5 years ago

A fascinating implication of this train of thought is the fact that "the ability to pair the return of one function call with another subsequent function call with some notion of continuation" sounds like pacts. Indeed, pacts could model this situation perfectly now, with yield and resume being supplied by SPV-specific replication, which would obviously have to serialize the closure too (at long last). To be continued ...

sirlensalot commented 5 years ago

Proposal

The specification for endorsement is now to leverage the trustlessness therein to automate cross-chain defpacts with the following features and goals:

Thus a "cross-chain invocation" of yield has the form (yield TARGET-CHAIN DATA). The data structure capturing a yield should be formalized to:

data Yield = Yield
  { _yData :: Object Name
  , _yTarget :: Text -- or ChainId newtype in Pact
  , _yEndorsement :: Hash
  }

Endorsement is a hash of

  1. The hash of _yData (to assure data)
  2. The hash of _yTarget (to assure target chain). Note #458 for better chain ID type.
  3. The executing PactID (to guarantee uniqueness of (data, target chain) pair, requires #456 )
  4. The hash of the defining module. Since this can easily create upgrade woes, verification can check all blessed hashes in verification. We can consider later adding metadata for being explicit about supported hashes (ie, @spv-hash ["a5435a355fd45sgd54d35as"] but for now the goal is maximum backward compat.

Yield replaces Term Name in current PactExec datatype (https://github.com/kadena-io/pact/blob/641542c16dfbd3806c4e646429e90027b1f3d07f/src/Pact/Types/Runtime.hs#L174).

Per changes in #451, will be part of an enhanced CommandResult that will form the receipt, and thus recoverable from SPV. CommandResult will also capture the module and defpact name as well as arguments as part of representing a continuation; the module hash will obviously be discovered using the continuation info.

Lastly, this should be accomplished in Pact as much as possible leaving just the shim for the backend, so that pact -s can be a good testing ground (through basically validating the ChainId and otherwise saying "welp, it must be good"), .repl tests can simply mock the chainId. Namely, endorsements is provided by Pact, and chainweb merely needs to consume the proof.

1 - The step modifier entity is tempting to overload, but it isn't meaningful on a first step (assuming the design labels the "target" (i.e., 2nd) step with the target chain -- could also label the "sending" step, and it's still cursed with this asymmetry). entity currently indicates a "private" pact by having all steps labeled with entity values.

sirlensalot commented 5 years ago

A final consideration is where "manual SPV", ie SPV that today would be supportable with spv-verify TYPE, fits into the far more harmonious world of defpact-driven SPV.

Proposals include:

  1. Model any SPV interaction as a second (third,fourth) step of a Pact. The idea here is that "pact is everywhere", and the first step presumably occurred on some other network/chain/TEE. It's in fact no different than cross-chain pact here modulo the additional assurance of endorsements within Chainweb. "One day all steps will run in Pact, no matter where they execute"; at a minimum you'd want to mock the semantics of the source for FV purposes.

  2. Endorsement, at least in the form of some kind of hash preimage or secret validation, seems flexible enough to support other auto-SPV endorsements if desired. For instance you could have an SPV only complete once you've written the preimage into the database, such that when the SPV arrives, the endorsement hash can be correctly built using that value.

  3. However, expecting endorsement to be automatic puts a large burden on the runtime, and limits customization in Pact. As it is, defpact-based SPV is hardcoded to "TXOUT", or "receipt-based SPV", and demands that the validating module be the same as the issuing.

  4. Realizing now endorsements could be validated by other modules, as it's trivial to look up another module's hash and use the same logic ... but this requires ID'ing the module. ~Could add ModuleName to the Yield output pretty easily.~ Done, added the name of the defpact, which enhances correctness in any case.

I think verify-spv as specified is valuable, namely to offer "TX_IN" for inclusion proofs as a "hey who knows" and leave the door open for implementing other verifications. My worry is its a black box (which is why @buckie wanted all of SPV to be in-pact, to support building other SPVs at the smart-contract level; the argument for at least automatic defpact SPV is cheap, fast, safe, brainless, ubiquitous, flexible, but for other SPVs building at smart-contract level is ideal, assuming its gas-efficient).

mercadoa commented 5 years ago

As per Emily, all done