A Proposal For a Flexibly Shielded RM Implementation

A problem with any implementation of a shielded resource machine is that all the data is shielded! Meaning if we wished to use partial transactions to make complete transactions we can not use the data included, this makes solving things very hard. As I understand there are active conversations on how to solve shielded transactions, this post is meant to be a compatible technique that can work with those methods but is a post on a technique on how to make transparent data shielded in a final transaction in a seamless way for the user.

For the sake of simplicity let us take a Resource data type

Resource = 
  { logic-reference, label-reference, value-reference,  quantity 
  , ephmeral?, nonce, nullifier-key-commitment, randseed }

The specification lays out alongside this fixed types, instead of taking fixed types, I’d rather take the types as an interface for us to care about. Meaning that if quantity is of type Quantity in the spec, then I take it to mean that every message a Quantity can accept the quantity slot in our Resource type can as well.

For clarification let us look at the Quantity type in some detail, let us say it’s an interface that just has a single value:

Interface Quantity = { 
  value : Quantity -> Field
} 

Now let us define out two different quantity types

ShieldedQuantity = { value }

ShieldedQuantity >> value
  ^ value

ShieldedQuantity >> value: a-value
  value := a-value shield

( Example Usage )

foo := ShieldedQuantity new value: 5.
foo value ( we get back 5 shielded )

This version is the shielded version, we can see that we respect the interface as we implement the value method. It simply returns the slot value. The only interesting thing is that our value: setter method takes a-value and calls shield on it such that it will be opaque from that point on.

Now let us look at a transparent version

Quantity = { value }

Quantity >> value
  ^ value

Quantity >> value: a-value
  value := a-value

( Example Usage )

bar := Quantity new value: 5.
bar value ( we get back 5 )

Here the only difference between this and the shielded one is that we get back the actual value from the object (5 in the case of bar value).

Now From Resource’s Point of view it doesn’t care, it may only ever call value on it’s quantity field

Resource >> quantity: a-quantity
   quantity := a-quantity

Resource >> amount 
   ^ quantity value

Thus we can have either

a-more-transparent-resource := Resource ... quantity: bar
a-more-shielded-resource := Resource ... quantity: foo

and all messages that we would want on these objects should just work!

Why would we want to give more transparent data? Well the rational may be to help solvers, maybe we want to give some information but not all information, and by doing this on a data structure by data structure basis we can effectively control how much information we divulge.

Now… this is unsatisfying, if we implemented our RM this way, we will have to deal with processing with transparent data and shielded data, even if this is possible, we have the problem that this information is stored for time in memoria. However I believe there is a very easy solution to this.

So I suggest we have another interface all data types that populate the RM data structures must accept. That is:

Interface Shield = { 
  shield : Shield -> Opaque
} 

Thus we can implement it like this


ShieldedQuantity >> shield
  ^ self

Quantity >> shield
   ^ ShieldedQuantity new value: self value

Thus we can do something like this

Resource >> shield
  quantity := quantity shield.
  logic-reference := logic-reference shield.
  ....

Now, when we send it to the intent pool we can decode some of the values we wish to be used in solving, and before it is submitted to the mempool, we simply need to say our-transaction shield, now all the data is now shielded to be stored online.

We can have many variants on this idea since we are using objects:

  1. If the particular RM allows transparent data, then if a user wishes it, they can implement a data structure that refuses to shield it’s data in case the transparent data wants to be preserved (I’m not sure why)
  2. We can have a more complicated a more generic shieldedQuantity that can determine what RM it wants to be used in and more effictively shield itself.
  3. What if we wanted to partially shield the data inside, meaning that we wish to keep some data always revealed..
  4. What if we can shield data to certain keys? Can we implement this with the proper data types?

What does everyone think of this technique?

Here are some interesting directions we can go:

  1. For resource-logics, if we imagine resource-logics as posing constraints, can we have a version of this that partially shields some of the constraints but not all? (growing on 2)
  2. Could we generalize the idea of a shielded resource machine data type and have shield-to: which takes a backend and knows how to handle that?
2 Likes

After discussing with @ArtemG I drew up a diagram to better explain these ideas:

Note for terminology: when I say transparent I mean it in the same sense that @vveiln does, for this post you can read transparent in this case as with hints, which @ArtemG has noted has helped this click for him.

Here the general idea is if you take a transparent data structure such as a point you can have a shielded version (one with hidden information) that reveals nothing of the structure. Further we can have a version that gives partial information.

For example we can talk about the following classes

(defclass point () x y)
(defclass partially-shielded-point x blob)
(defclass fully-shielded-point blob)

These classes could implement the method shield

(defmethod shield ((p point) key)
   (make-fully-shielded-point (encrypt-with key p)))

(defmethod shield ((p partially-shielded-point) key)
   (blob p))


(defmethod shield ((p fully-shielded-point) key)
   (blob p))

And example for having an action with hints: given an action

%Action{logic_proofs: %{123 => {false, vk, proof, appdata, appwitness}}, cus: ...}

the corresponding hint-action is

%Action{logic_proofs: %{123 => {false vk, {%{self => resource}, proof}, appdata, appwitness}}, cus: ...}
where commit(resource) = 123 

you can make the hint structure canonical by making it a datatype that has fields adequately named to reflect the needed inputs for the witness, i.e

%{self => ..., nk => ..., created => ..., consumed => ..., appinputs => ...}

where the struct need not bear the entirety of the witness info

1 Like

Thank you for sharing. I like the interface idea.

I recall a similar scheme and topic close to your proposal. Your approach is more complete and practical for our RM architecture. Typically, the receiver holds the full key to decrypt their resource-info. Special keys, called viewing keys, allow partial decryption and access to limited information. Solvers can use viewing keys to obtain only the necessary info for intent solving. Other actors may also require specific revealed data. All access control can be managed through precise key management, ensuring each actor receives only the necessary and limited info. We previously discussed this within the cryptography team but didn’t finalize a solution. Perhaps we can collaborate to integrate your proposal with existing cryptographic key management solutions.

Revealing partial constraints can be challenging. One possible solution is to split constraints into multiple logics or proofs, revealing only some sub-resource-logics. However, this approach increases the number of proofs and incurs additional costs. Anyway, it’s really an interesting research topic.

2 Likes

You are right. Solving for shielded transactions is something very challenging. We currently don’t even discuss partial solving (“proper” solving) for shielded transactions as it practically means, in the given context, that the solvers just must share all of the information, which is kinda equivalent to one-step solving.

I think practically speaking we have to give all information because even if it isn’t needed for matching, it is needed for proving which solvers perform once they find a matching intent. Or does the suggested approach cover this issue?

I’m also not sure there is a notion of a shielded resource. If we are speaking of resource objects (what we call a resource object in the RM spec, not related to the OO language concept), they are always transparent in some sense. The difference is that in shielded transactions resource objects are not included explicitly. They can be transferred as a part of the transaction encrypted, but this is a part of settlement deal, and here we are talking more about intent matching.

I believe the only way to enforce a satisfaction of multiple sub-logics in the current design is to have a resource associated with each (otherwise we can’t make sure it actually was verified in the current design), which is equivalent to not having sub-logics