Contract signatures

Can I call directly a contract (stamped_value) or do I always need to go through another contract that calls STAMP_VALUE?

We can build replay protection in directly by making it non dupable. However, for that to be possible the read must not be destructive.

READ_STAMPED_VALUE :: 'a stamped_value : 'S -> (pair (pair address 'a) 'a stamped_value) : 'S

Not clear what you mean

I initially thought something along the line of having something like STAMPED_SENDERS, that would take care itself of replay protection and return a list of SENDERS and associated message.
Possibly by having a TRANSFER_WITH_STAMP operation.

We can build replay protection in directly by making it non dupable.

How about PAIR ; DUP ; UNPAIR ; DIP UNPAIR? Shall we add dupable as a new Michelson type attribute?

Yeah, non dupable (or rather “singleton”) would have to be yet another Michelson attribute, for some reason I thought that was already a thing.

The alternative, as you describe, doesn’t seem to attach a specific piece of data to the stamp.

Let’s assume contract A is

parameter (stamped_value nat);
storage (pair address nat);
code { UNPAIR; READ_STAMPED_VALUE; ASSERT_CMPEQ }

If the stored address is the one of an implicit account, is there a way successfully call this contract (from this address)?

We could either decide that stamping is restricted to smart contracts or allow stamping from implicit account by considering any literal of type 'a as a valid literal of type stamped_value 'a

Ah, I see what you mean. Yes, you’d be able to specify a stamped value literal in a transaction. I wouldn’t automatically convert a literal to a stamped literal, but if I sign a message from an implicit account passing a stamped literal, well that literal is clearly stamped.

I am writing a dummy prototype, where each message is associated with a string.

TRANSFER_WITH_STAMP takes bytes as an additional argument.
(Actually, a map(string,bytes), so that you can stamp multiple things. Each with a clear meaning.)

It’s not clear what you’re proposing. Is TRANSFER_WITH_STAMP the same as transfer tokens? So you’ve transferred stamped bytes, now what? Can they be retransmitted? If not, what’s the point, if yes, what’s to prevent them from being transmitted twice?

I like preventing the duping of values contained stamped_values best, but if we don’t want that complexity (something something linear type) here are two other options:

  1. Add an existing RIP instruction which “rips” a stamped value. The stamped value can still be passed around and everything, but it can only be ripped once. RIP could either FAIL if called on an already ripped value, or perhaps return a boolean: true the first time, and false thereafter. During the execution of a transaction, a global map of all stamped_value IDs is kept by the interpreter, across different invocations, to keep track of which ones have been ripped and which ones haven’t. To be clear, this isn’t a Michelson map, it’s a global value maintained by the interpreter for that transaction.

  2. Put the onus of enforcing non replayability on the contract receiving the stamped value by having it maintain a counter.

I’m not crazy about 1 because it creates a shared global state between different contracts as they get executed. I can’t think of anything bad that would happen, but it’s not good practice to do such things.

I’m not crazy about 2 because it would hand developer a powerful footgun.

Suppose I have a stamped_value (pair (address %from) (address %to)).

Can I CAST it to a stamped_value (pair address address), and then to a stamped_value (pair (address %to) (address %from)) (or whatever)?

Or, maybe, the definition of “compatibility” of types (as used in CAST and elsewhere) will have a special case for stamped_value, enforcing a stricter notion of compatibility for the stamped type?

I’m less familiar with how annotations are used inside Michelson, and I know that they have evolved in importance. If I had to pick one, I would not let this be compatible, but I don’t think it’s a big issue if it is compatible.

I suspect you’re worried about confused deputy attacks but I think that, in general, the address of the intended recipient would almost always be a part of the stamped value.

François, the SmartPy creator pointed out that “non dupable” would not allow these to be extracted from pairs. This probably isn’t an issue if we have a native UNPAIR instruction.

He proposes, alternatively, that the contract simply fails if multiple copies of a stamped_value are present in the list of passed operations.

He also suggests that not building in replay protection in stamped_value and promoting good patterns for their consumption would work as well (and let stamped_value be storable).

Just spent a few hours prototyping, doesn’t build yet, but it is available here.
The current choices I have to make:

1. There is no concept of “type of values that can be passed around in parameters, but can be created only by smart-contracts” in Michelson. Without one, users could build fake stamps (because a user can create all the values that a smart-contract can). So, we would need to add one.
This can be avoided if stamps are not passed around, but only references to stamps, stored in some other place.
So I need to make a choice there. Both make Michelson more complex.

2. There is no concept of “local effects” in Michelson. Effects in Michelson are kind-of opaque, because they can only affect context, a catchall value for all effectful functions in Tezos.
Concretely, step in Michelson has this type: step : type before after. context -> (before, after) code -> b stack -> (a stack * context). context has for instance a variable holding the remaining gas.
I would like to avoid adding more to this. So, I have another to choice to make:
- Working on adding some auxiliary type capturing local effects
- Using some other solution like the free monadic interpreter, being worked on at NL


Suggestions are welcome!

Can we think about this in terms of migration paths?

I feels like the simplest solution to implement is one where handles are passed around and point to a structure that’s global to the execution of the transaction across multiple operations, with opcodes to read and write from that table. I would suggest no deallocation, just a seal on each value that can be intact or broken (or a counter).

It’s not as elegant as passing values around, but…

Can we think of a way to implement the pointer approach, that would be transparently upgradable to the fancier version?

Or do both version require an equal amount if sweat to integrate in Michelson?

That is the preliminary conclusion I am reaching in the previous message.

Do you mean this user-wise or implementation-wise?

If both variants are equally painful to implement, that doesn’t matter and one what might as well start with the cleanest version.

Still working on that cleanest version. It is essentially about making a more local state monad in script_interpreter.ml . A more local error monad will also naturally follow.

Just so you know I think Raphaël C is taking a crack at it as well… the variant where the stamped values can be duped but the transaction fails if a sealed value is passed more than once or to more than one contract in the list of transaction returned. This was the solution suggested by François and consensus seems to be forming around it. There is already a runtime check to ensure that operations are not duped, it’s a somewhat reasonable extension to ensure that duped stamped values aren’t passed either. The static benefit that we would get from making stamped values into linear types can be easily replicated by running a static analyzer on the code.

Dan Robinson pointed out a hackish way you could get this without a protocol change.

A contract could store a large table of “contract signatures”. Any contract can call it and write a message on it, and pass around a handle to that message. It’s clunky and requires a bunch of callbacks but it works.

To be clear, I don’t recommend this approach, just good to know it’s a possibility.