Indicate list of OR signatories

What is the best or most accepted pattern to have a template with a signatory that can be one amongst a few potential signatories? (one amongst Jean, Pierre or Paul could be a signatory of a contract - is that feasible?)

Yes, that’s possible, and there is a blog post on the topic. TL;DR:

import DA.Next.Set as S

template NofM
  with
    sig : Party
    n : Int
    m : Int
    controllers: Set Party
  where
    ensure S.size controllers == m
    signatory sig
    observer controllers

    choice C : ()
      with
        actors : Set Party
      controller actors
      do
        assertMsg "Unauthorized actors" (not (S.null (S.difference actors controllers))) 
        assertMsg "Too few actors" (S.size actors < n)
        -- Do something
        return ()
1 Like

@cocreature pointed out to me that I may have misread your question. Is this what you are after or are you looking for a way to restrict what parties that can go into the signatory clause?

The latter is possible, but I’d consider it a hack. There’s a discussion of the topic here. TL;DR:

import DA.Optional

template T
  with
    sig : Party
  where
    signatory sig
    ensure sig `elem` mapOptional partyFromText ["Jean", "Pierre", "Paul"]

It’s a hack because you are hardcoding the string party identifiers into the template. That will make the template highly non-portable.

2 Likes

Thanks a lot for this. It does not solve the problem. Not sure that I can be given DAML’s structure.

Expressing these sorts of model-constraints in DAML is always interesting. In order to allow a DAML system to scale, the DAML ledger model imposes a couple of strict limitations (system-constraints) on how you express constraints. In my experience, while this can be frustrating, thinking through and accommodating these within your design generally improves both your understanding of your business problem, and more importantly, makes your final design less brittle and more adaptable to future requirement changes.

  1. The only thing a DAML transaction knows about is what it can read from on the ledger, and the only way it can know of something on the ledger is to be explicitly told about it. This means that with exactly one exception, all constraints in DAML must be expressed existentially. The only universally quantified constraint in DAML is the key-uniqueness constraint—and even that required introducing the whole maintainer semantic and key maintenance protocol.

  2. With the single exception of key-uniqueness (again), all constraints on the creation of a new contract must be evaluated within the static-local scope of the contract itself.

Both of these ensure that a creation/exercise of a contract/choice can be evaluated and validated with reference to a bounded set of contracts

In order to permit DAML to scale to large numbers of parties, all constraints in DAML must be expressed existentially A constraint on a contracts, and that therefore the submitter can never be surprised at the result of a transaction by being informed of the presence of a contract about which they were not aware*. This is important to allow DAML to scale effectively as a distributed system.

So, to think about your problem:

  1. templates don’t have signatories, only contracts do. So for the rest of this answer I’m going to assume you want to ensure “a contract with a signatory amongst …”.

  2. Recall system-constraint 1. The only thing that DAML knows about is what it can read from the ledger. That means that this idea of “parties from which the signatories must be drawn” must exist on the ledger somehow. Note that DAML is a symmetric multi-party system. Unlike traditional architectures there is no privileged perspective, no super-user, no a priori centre (pulling on this thread ends up with us buried in post-modern deconstructionism, so I’m going to stop there).
    The result is that a DAML model (ie. template) can’t express the idea that you are privileged in anyway, that any model that can express the constraint Jean restricts contracts to “Jean, Pierre, or Paul” can also express the constraint Andrae restricts contracts to “John, Peter, or Paula”. Because to do otherwise would be to express a universal property of the ledger, and we are restricted to existential predication.

So we need to existentially model the context of our constraint. Specifically, the perspective of the predicating speaker (Jean or Andrae) must be explicitly modelled. As in all modelling this can be achieved in at least two ways, intensionally or extensionally. In my experience DAML is at its best when used intensionally so I’ll cover that first.

The intensional model is focused on behaviour and implication. We don’t model state as data, but rather as the set of behaviours (choices) and constraints on that behaviour implied by previous acts. This approach sees DAML as a means of achieving coherency of praxis in a symmetric distributed system while denying a central authority (and pulling on this thread leads into semiotic pragmatics, so again I’ll shelve it).

What this means is that if there is a contract on the ledger then there was at some point a consensus on potential acts one of which has caused the creation of this contract.

Ie. If there is a contract:

-- (Note the explicit modelling of the `predicator`, there is no implicit authority you can appeal to, there is nothing that is not on the ledger).

template OneOfN
  with
    predicator: Party
    signer: Party
  where
    signatory predicator, signer

Then there must have been the right to create this contract…

template RightToCreateOneOfN
  with
    predicator: Party
    potentialSigners: Set Party
  where
    signatory predicator

    nonconsuming choice CreateOneOfN: ContractId OneOfN
      with
        signer: Party
      controller signer
      do
        create OneOfN with predicator; signer

…which allows you to now utter the predicate: Jean requires that all OneOfN contracts within their authority must be signed by one of the specified signers. Note that the constraint itself is not modelled as data on the ledger, but as a choice, a right to act that has been authorised by whatever prior choice/right-to-act led to the creation of that contract.

Yes, intensionally it’s turtles all the way down until you get to self-authorising unilateral speech acts that therefore do not require or represent consensus.

The alternative is to model this extensionally, This represents the state of the system as data, and constraints on the system as constraints on the permitted values of that data. So naturally this has to comply with the system-constraint 2. One way of rewording constraint 1 is “All intensional constraints must only be existentially predicated”; similarly constraint 2 can be expressed: “All extensional constraints must be contract local”.

So if we want to constrain the signatory of a contract to a set of parties, that predicate must be expressed exclusively in terms of data in that contract. So:

template OneOfNLocal
  with
    signer: Party
    potentialSigners: Party
  where
    signatory signer
    ensure signatory elem potentialSigners

Now, you will immediately observe that while this constraint does encode the letter of the requirement, it falls well short of the spirit. As mentioned above, DAML prefers to be an intensional modelling language. The root of this difficulty is the lack of a central authority. So as a single-signatory template, this represents not consensus on predicated state, but rather a unilateral speech act by signer, and here we see Eco’s observation that any system that can express a truth can by necessity express a lie.

It is for this reason that ensure clauses are rare in DAML modelling; however, I didn’t want to ignore them in this discussion as there are times when this sort of constraint is appropriate — generally to guarantee preconditions on the contract’s choices (ie. a list/set is non-empty, a number is positive or non-zero; etc).

Summary: DAML is symmetric and un-centred, as such modelling must be explicit about authority, existentially predicated, and invariably intensional. This is a significant departure from traditional modelling which invariably has an implicit central authority, admits a range of universally quantified predicates, and is oriented around manipulation of extensional data — but this departure is a good thing and essential for scalable, distributed application of the resulting models.

5 Likes

Thanks a lot for this very long yet very precise answer. I do think however that you are splitting hair with my terminology. If people should not refer to template but rather to contract, please change the keyword template to contract. That would surely make communication easier.

1 Like

@Jean_Safar Was your question whether the following is possible?

template NofM
  with
    sigSet : Set Party
  where
    signatory (any one from sigSet)

s : Scenario () = do
  sigs@[jean, pierre, paul] <- mapA getParty ["Jean", "Pierre", "Paul"]
  let sigSet = Set.fromList sigs
  submit jean do create MofN with sigSet
  submit pierre do create MofN with sigSet
  submit paul do create MofN with sigSet
  return ()

If so, the answer is no. The signatories of a contract have to be computable from the contract arguments only. The signatory declaration really just desugars to a function signatory: MofN -> [Party]. This is true for all template annotations: observer, key, maintainer, ensure, agreement are all just functions from the contract arguments to something. In other words, contracts on the ledger are not annotated with this information, but this information is computed from the contract.

Thanks a lot @bernhard … The best way I can express what I am after (and apologies in advance for the potential stupidity my pre-weekend thoughts …) is that I am attempting to craft a template/contract where the choice controllers are not parties (Party) but but roles. And the set of parties (Party) that can have that role at any given time is completely dynamic. That yields very complex designs.

For example, I would like to have a template/contract (say Trade) in which a given choice, for example bookTrade, would necessitate a second pair of eyes to approve the trade. I would therefore create a TradeApprovalRequest, but I would need to one approver within my organisation to be able to approve or cancel that trade booking.

So in Summary, I am looking for parties (Party) to be more complex and have a notion of RBAC in them … and maybe that’s completely not kosher, but I think that’s what I am facing, phrased a general manner.

Does it make sense at all?

Ok, if I now understood you correctly, this sort of thing can be implemented on the authorization side, but it’s currently tricky on the privacy/visibility side as changes to the group/role have to be propagated through to the (immutable) contracts that such changes affect.

Ignoring visibility issues, we could implement this:

template TradeApproverGroup
  with
     admins : Set Party
     members : Set Party
     groupName : Text
  where
     signatory admins
     observer members
     key (admins, groupName) : (Set Party, Text)
     maintainer key._1

     choice AddMember ...
     choice RemoveMember ...
     choice PromoteMember ...
     choice DemoteAdmin ...


template TradeApprovalRequest
  with
     group : TradeApproverGroup
     requester : Party
  where
     signatory requester
     observer group.members

     choice Accept : ContractId TradeApproval
     with
       approver : Party
     do
       (groupCid, actualGroup) <- fetchByKey @TradeApproverGroup (group.admins, group.name)
       assert (approver /= requester && group == actualGroup)
       create TradeApproval with ..

The problem in the above is: If the group gets updated whilst there is an outstanding TradeApprovalRequest, how do you update the group on the request?

A proposed solution is to allocate parties for such groups:

template TradeApproverGroup
  with
     admins : Set Party
     groupParty : Party
     members : Set Party
  where
     signatory admins, groupParty
     observer members
     key groupParty : Party
     maintainer key

     choice AddMember ...
     choice RemoveMember ...
     choice PromoteMember ...
     choice DemoteAdmin ...


template TradeApprovalRequest
  with
     group : Party
     requester : Party
  where
     signatory requester
     observer group

     choice Accept : ContractId TradeApproval
     with
       approver : Party
     do
       (groupCid, actualGroup) <- fetchByKey @TradeApproverGroup group
       assert (approver /= requester && approver `elem` actualGroup.members)
       create TradeApproval with ..

The feature that’s missing to make that work is for all members to be able to read as the group party over the API so that they can see the TradeApprovalRequest. Sadly, this is not possible yet so for now one has to keep the membership relatively stable, or propagate changes through to the observers.

2 Likes

You could conceivably side-step this issue by not mandating that group == actualGroup, but instead just that (elem approver actualGroup) && (elem requester actualGroup), i.e. it does not really matter that more people have been added or that unrelated people have been removed, as long as at the time of approval the approver is authorized to approve (and the requester is still authorized to request).

1 Like

Perhaps the biggest shift in perspective moving to DAML from more traditional approaches is that, when creating a DAML model (= set of templates), you are setting up potential relationships between parties. At runtime, when the application is live, those relationships will be brought to life in the form of contracts recorded on the ledger, which themselves represent that some of the participating parties have committed to specific agreements with other parties.

You are not setting up one application, but the possibility for many peer-to-peer applications. Therefore, in the DAML code itself, you cannot (really: should not) express any global property about the parties. In that sense, what you’re asking for is not (easily) supported: you are not expected to use DAML to define a template and, at the code-definition level, mandate that only the specific parties Jean, Pierre and Paul are able to create contracts based on those templates.

What you can do, as @bernhard’s response illustrates, is set up the templates such that, at runtime, some (larger) group of parties can express that they trust some presumably smaller group of users to do some things.

It’s really hard to be more precise without knowing more about exactly what you wanted Paul, Pierre and Jean to be able to do.

4 Likes

hey guys, throwing my two cents in here. I personally have the same issue @Jean_Safar has. I would very much like to be able to restrict the population of parties that that can instantiate a template into a contract.

The thing is, I would not express the role in DAML code but as an attribute of the party when the party is provisioned. So parties could have a “roles” attribute and so I would be able to write

ensure hasAttribute signatory "role" "broker"

where signatory is the party that instantiates the template into a contract “role” is the attribute I need to check and “broker” is the value I expect it to have.

Another such attribute that we could think about is “clearenceLevel”. I want parties who observe a contract in the organization to have a certain minimum clearance level.

And if you think about it it’s a very natural thing to do. In real life, only parties with specific roles can enact certain contract templates. Only a notary can create a real estate transaction contract. Only a broker is allowed to submit an order to the exchange. I, personally, can pull a contract template off the shelf and fill it in, nobody is going to take me seriously.

Now in real life that’s not a huge problem, Ultimatelly after realizing everybody is ignoring me I will throw my contract draft to the bin and off I go with my life.

On a ledger, however, if we allow any party to create any type of contract, that’s something a maliciuos party can use to spam the ledger with meaningless contracts. And somebody may not realise that @Jean_Safar is not really a notary and accept the real estate transaction contract proposal. That’s basically a phishing attack on the ledger and that may result in fraud ( sorry Jean ).

This is why I think the ability to assign roles to a party when it is provisioned, in the same way a bar would issue a license to practice law or the financial markets authority would issue a banking license, would be highly beneficial.

2 Likes

Thanks @entzik … I would add to what you wrote the need to have parties a hierarchical nature . That would allow you, together with your role definitions, to correctly express a signatory or observer (for example, all traders within the fx desk of the London Branch of Bank X could approve a trade) while the trace of the action would still be the actual party that performed the action. That would be great to have that …

1 Like

@entzik, your real world analogy works very well here. Your proposal is to assign roles outside the ledger via more fine grained user management via attributes on parties. Let’s think about a few scenarios that we’d run into:

  1. What happens if Alice has role broker, creates a contract with the ensure clause, and then loses the broker role. Does the contract become void? Does it stay around because the ensure clause is only enforced at the point of creation? Can the role not be taken away until there are no active contracts of that type?
  2. Who administers these attributes in a distributed setting? Who decides that a notary is a notary or a broker is a broker?
  3. How do you make sure the ensure clause can be validated in a deterministic way? Suppose I set an attribute for Alice and she immediately fires off a transaction that needs that attribute, how can we ensure there is consensus about whether the attribute setting happened before or after the transaction?

A lot of these questions can be resolved quite easily by making roles an on-ledger concept. Ie put your attribute in a contract:

template PartyAttribute
  with
    giver : Party
    holder : Party
    name : Text
    value : Text
  where
    signatory [giver, holder]
    key (holder, name) : (Party, Text)
    maintainer : hey._1

We have now completely cleared up items 2. and 3. above. You may argue that we have merely shifted the problem from restricting who can act as a notary or broker to who can sign attributes. That’s true, but because most roles are hierarchical, you get to a point where there is just one special party, or a small number of special parties very quickly, which are effectively your roots of trust when it comes to attesting party roles. You can distribute that trust explicitly by requiring multiple signatures on Attributes before accepting them (ie givers : [Party] instead of giver : Party).

We are still left with issue 1. Currently we don’t allow fetching contracts in an ensure clause, which means you can’t write

template BrokerContract
  with
    broker : Party
    brokerAttestator : Party
    ...
  where
    signatory broker
    ensure do
      attribute <- fetchByKey @PartyAttribute (broker, "role")
      return ((attribute.giver = brokerAttestator)
        && (attribute.name = "broker"))

The reason this isn’t possible is in part because the semantics are quite unclear. It would be easy to check this at the time of creation. But then what? We run into exactly the questions in item 1. What happens if the attributes change? Suddenly we have a contract with an ensure clause that would no longer execute successfully. ensure is meant to be an invariant that always holds.

In part, this problem arises because we have tried to distribute the information that the party is a broker. There is now a contract Attribute that makes a claim on this, and a contract BrokerContract. Item 1 really boils down to keeping these in sync. But here my question: why spread this information out in the first place? The Attribute contract seems like a pretty sound source of truth on the matter.

Instead of stopping parties from signing contracts, we can stop them from doing anything useful with them unless they have matching roots of trust.

template BrokerContract
  with
    broker : Party
    brokerAttestator : Party
  where
    signatory broker

    let
      checkRole = do
        attribute <- fetchByKey @PartyAttribute (broker, "role")
        return ((attribute.giver = brokerAttestator)
          && (attribute.name = "broker"))

    controller broker can
      Broke : ()
        do
          checkRole
          ...

You can potentially even save yourself from needing to thread through the roots of trust (brokerAttestator in this case) by just checking that contracts have matching roots of trust when they are used together. I’d describe this pattern as an “access token” pattern. The Attribute here is really just an access token to the choices on BrokerContract

Now there are other solutions I’ve seen discussed for cases where the semantics of the ensure clause would be clear and or we can weaken our general trust assumptions. Do you know how you’d answer the questions in 1.? If so, maybe I can dig out some of the proposed solutions just to see whether they would have fitted your needs.

2 Likes

Hi @entzik,

None of the examples are really properties of the parties themselves, they are just who you decide to trust. You trust the bar to decide who can be a lawyer; you trust the system that designates notaries, etc.

I’m not saying this is always the most convenient approach in the short term, but the approach taken by DAML is to make these kinds of trust decisions explicit as contracts, rather than some ambient, global property of the ledger.

This can be cumbersome, but it can also have advantages: what happens when you expand to a new country, and suddenly you have two separate bars, and not everyone wants to trust both? And not every lawyer will be qualified to operate in both countries. And most disputes will have to be resolved in either one or the other, but perhaps some are cross-cutting?

1 Like

What if there was an optional onCreation field that one could specify, that only performed the Update on instantiation. This would let you distribute the enforcement of the invariant to the creation and choice exercise as needed.

While I largely agree with your and @Gary_Verhaegen’s point that the hard coding of roles and trust within a distribute setting is difficult, it would be helpful for us to find a middle ground for DAML users who might use a private DLT.

3 Likes

The example of expanding to a new country does not require a distributed system.

Think of it as avoiding global variables. You’re always better off not building in the assumption that there is only one thing, because eventually you need a second one, and a way to say which one you’re talking about. That’s just as true for the bar that certifies lawyers as for the database connection of your application.

1 Like

How do you verify that? Imagine this template:

template T
  with
    bernhard : Party
    leonid : Party
  where
    signatory bernhard
    onCreation do
      lookupByKey @TCreationAuth (leonid, bernhard)
    key bernhard : Party
    maintainer key

Now suppose I later fetch this contract in a context with gary, but without you:

template TFetcher
  with
    bernhard : Party
    gary : Party
  where
    signatory bernhard
    observer gary

    controller bernhard can
      FetchT : T
        do fetchByKey @T bernhard

I’m the only signatory on the T. How does gary know the onCreate was ever even run? He somehow has to get verification from you that you validated the onCreate way back when. The two ways of doing that I can see are

A) Ask you every time the contract is fetched, which means you have to remember this contract forever since you don’t get to find out when it gets archived
B) Put your signature on the contract. We have a mechanism for that :wink:

I’m not trying to be contrary or obtuse here. I understand both the use-case and the sort of feature you are after, and we have put some cycles into thinking about solutions without coming up with something truly convincing that is equally as safe as the rest of DAML.

1 Like

Why does he need verification from me? bernhard is the only signatory on T ?

What if T isn’t general, but specific’ BernhardSpecialContract ?

1 Like

If you are not going to verify the onCreate block, why have it? Then you can achieve the same thing with a “smart constructor” function:

createT (t : T) = do
  fetchByKey @TCreationAuth (t.leonid, t.bernhard)
  create t
1 Like