Is issuer always a signatory on asset contracts?

The skeleton Daml project includes this template…

template Asset
  with
    issuer : Party
    owner  : Party
    name   : Text
  where
    signatory issuer
    observer owner

…where the issuer is a signatory.

As another example, the Amulet model (used for Canton Coin) includes this…

template Amulet
  with
    dso : Party
    owner : Party
    amount : ExpiringAmount
  where
    signatory dso, owner

…where the dso is a signatory with the owner.

Conceptually, this means that the issuer is “involved in” every (sub-)transaction that archives and/or creates this asset. For example, for UTXO-style contracts, the issuer is involved in mints, splits, transfers, etc.

Question: :thinking:

Is it possible in Canton to create a smart contract in which an issuer mints the asset, but is not required to verify splits, transfers, etc.?

Model the minting as an issuer-signatory contract/choice, and the spendable asset as a template whose only signatories are the owners, with transfers/splits controlled by those owners. The issuer then does not need to be involved in later transactions.

But then anyone could create a self-signed contract of the spendable asset template. In other words, I could print my own money.

An owner-only UTXO can be created, but it won’t be validly accepted if your model requires a verifiable mint proof from the issuer. Mint coins via an issuer-signed authority that embeds a serial/nonce and signature (or a registry entry), and make recipients’ Accept choice verify that proof before creating their new UTXO, self-minted coins fail this check and are rejected. Thus the issuer signs only the mint, stays out of transfers/splits, and security comes from verification on acceptance, not co-signing every spend.

But if the UTXO needs to be split, how do the two new UTXO’s get their “embedded serial/nonce and signature (or a registry entry)?”

You don’t drag the issuer back in, splits inherit the original mint proof and prove validity by lineage + conservation: each child UTXO references the parent id, the parent is archived in the same atomic transaction, and the children’s amounts sum to the parent. If you want tags, derive them deterministically (e.g., child_tag = H(parent_serial || index)), or index them in a registry keyed by the original issuer serial; either way, acceptance checks the chain back to a single issuer-minted ancestor and rejects anything that doesn’t add up. Net: issuer signs once at mint, and every later split/merge is validated by parent linkage and arithmetic, not by re-minting signatures.

But in Canton, the receiver will not have visibility to the parent UTXOs.

Wrong, Canton gives you two options: disclose what they need or make the child self-contained. During the split, explicitly divulge the parent UTXO(s) to the receiver in the transaction (disclosedContracts/divulgence) or embed the parent hash + issuer mint proof (or registry key pointer) into each child so the receiver can verify lineage without direct parent visibility.

But the parent UTXO(s) will have been archived. The recipient won’t be able to verify it actually existed or exercise any choice on it. Thus the receiver will not be able to verify that the parents’ full value has not been assigned to other children and that the token being passed to me is not a duplicate.

With either approach (explicit disclosure or embedded lineage), an owner-as-only-signatory model doesn’t prevent me from duplicating a valid asset and spending it at two different places.

No, double-spend is killed by the ledger, not by the recipient poking archived parents: the split transaction atomically archives the parent and creates the children, and the synchronizer’s total-order + single-consumption rules ensure only the first spend that consumes that parent can ever commit; any concurrent/duplicate spend referencing the same parent simply fails (parent already archived / key conflict).

Your recipient only needs the child’s issuer-mint proof plus the transaction that consumed the parent (via divulgence or embedded lineage hash) to know the value wasn’t cloned, because if someone tries to “spend it twice,” Canton rejects the second one at commit time.

You seem fully convinced. :slight_smile:

Care to create a GitHub repo with a sample demonstrating your proposal?

Sounds good, happy to put this into a minimal, runnable repo so we can ground the discussion.

1 Like

Hello @WallaceKelly , kindly refer on this GitHub repository link for the demonstration: OwnerOnlyUTXODemo.

Thank you, @eslaboncarl, for going the extra mile and creating a code sample for us to discuss.

My point is that with this code…

-- Offer/Accept on existing Coin.
offerAcceptWorks : Script ()
offerAcceptWorks = script do
  (issuer, alice, bob, _) <- setup

  maCid <- submit issuer do 
    createCmd (MintAuthority with issuer)

  offerCid <- submit issuer do 
    exerciseCmd maCid (Mint with owner = alice, amount = 50, serial = "SER-002")

  coinCid <- submit alice do 
    exerciseCmd offerCid AcceptMintOffer
    
  offer <- submit alice do 
    createCmd (Offer with coin = coinCid, owner = alice, to = bob)

  _ <- submitMulti [alice, bob] [] $ exerciseCmd offer Accept
  
  pure ()

… we get the following Coin contracts:

That looks good.

However, the model doesn’t prevent Alice from duplicating the minted coin. That is, Alice could do this before creating the offer to Bob…

  Some coin <- queryContractId alice coinCid
  dupCid <- submit alice do
    createCmd coin

…which duplicates the minted coin. She can then send 50 to Bob and still keep 50 for herself.

We could route all value movement through Offer.Accept and make that choice verify an issuer mint-proof/serial (or registry entry) plus lineage/conservation (parent consumed in this tx, sums match), so any naked createCmd Coin is unspendable. Concretely, add an issuer proof at mint, carry it through splits, check it in Accept, and remove any alternate transfer paths that bypass this verification.