onflow / cadence

Cadence, the resource-oriented smart contract programming language 🏃‍♂️
https://developers.flow.com/cadence
Apache License 2.0
526 stars 137 forks source link

Add Convenience Methods for Signed Data Structures #3388

Open dete opened 1 month ago

dete commented 1 month ago

Issue to be solved

A frequent use-case in blockchains is to have a user sign some data structure, similarly to how we asks users to sign transactions. Depending on the application, the data structures could be very different. They could be as simple as a few integers, or it could be a complicated structure with nested arrays and dictionaries.

Ideally, there would be a way in Cadence to easily handle such data structures: make it easy for client software to generate signed structures, and make it easy for smart contracts (or off-chain code) to verify those structures.

Suggested Solution

I'm imagining a wrapper mechanism similar to Capabilities. If I had a structure called Foo, Cadence code could refer to Signed<Foo> datatypes. Signed<Foo> instances would look and act just like the base type with a few caveats:

The resulting code could look sort of like this:

access(all) struct AuctionBid {
    access(all) let bidderId: UInt64
    access(all) let auctionId: UInt64
    access(all) let bidAmount: UFixed64
}

access(all) fun submitBid(_ bid: Signed<AuctionBid>) {
    let bidder = auctionManager.getBidder(bid.bidderId)

    if bid.verify(bidder.keyList) {
        let auction = auctionManager.getAuction(bid.auctionId)
        auction.processBid(bidder, bid.bidAmount)
    }
}

As a rule, Signed<> structures would be constructed off-chain and serialized to be used as arguments to scripts and transactions, so we'd also need to make sure that any client libraries had convenience methods for constructing signed objects. I don't think it would typically make much sense for Cadence code to construct Signed<> instances directly. I don't believe signed resource objects are useful (or perhaps even possible).

The type of the wrapped object could be used as a domain tag to prevent cross-domain attacks.

Open Question: What (if any) protection against replay attacks can/should we provide here?

dete commented 1 month ago

For clarity: This is merely a feature suggestion, and should not be considered until after C1.0…

turbolent commented 1 month ago

Great idea!

I guess access on fields would return references, so that e.g. containers (arrays, dictionaries, nested composites, etc.) would be immutable?

bluesign commented 1 month ago

If we add hash() method to struct we can make a Signed struct, then we don't need to worry about mutability.

It would be nice to be able to generate signed stuff from Cadence side too. ( if there are multiple signers for example )

access(all) struct Signed{
  access(all) var payload : AnyStruct
  access(all) var signatureSet: [Crypto.KeyListSignature]
  access(all) fun verify(keyList: Crypto.KeyList): Bool {
       keyList.isValid(
        signatureSet: signatureSet,
        signedData: self.payload.hash()
    )
  }
}