Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SignedTransaction type #529

Merged
merged 19 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions haskell-src/Concordium/Cost.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# LANGUAGE DerivingVia, TypeFamilies #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE TypeFamilies #-}

-- |
-- Definition of cost functions for the different transactions.
Expand Down Expand Up @@ -237,11 +238,11 @@ initializeContractInstanceCreateCost = 200
updateContractInstanceBaseCost :: Energy
updateContractInstanceBaseCost = 300

-- |Maximum amount of nested V1 contract calls. That is, the maximum amount of
-- execution frames that need to be kept alive at the same time.
-- | Maximum amount of nested V1 contract calls. That is, the maximum amount of
-- execution frames that need to be kept alive at the same time.
--
-- Since each frame that is kept alive can consume up to 32MB of memory this limits
-- the worst case memory use of contract calls.
-- Since each frame that is kept alive can consume up to 32MB of memory this limits
-- the worst case memory use of contract calls.
allowedContractCallDepth :: SProtocolVersion pv -> Word -> Bool
allowedContractCallDepth spv n = demoteProtocolVersion spv <= P6 || n < 384

Expand Down
17 changes: 17 additions & 0 deletions haskell-src/Concordium/Crypto/EncryptedTransfers.hs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,15 @@ instance FromJSON EncryptedAmountTransferData where
eatdProof <- v .: "proof"
return EncryptedAmountTransferData{..}

instance ToJSON EncryptedAmountTransferData where
toJSON EncryptedAmountTransferData{..} =
object
[ "remainingAmount" .= eatdRemainingAmount,
"transferAmount" .= eatdTransferAmount,
"index" .= eatdIndex,
"proof" .= eatdProof
]

withEncryptedAmountTransferData ::
EncryptedAmountTransferData ->
(Ptr ElgamalCipher -> Ptr ElgamalCipher -> Ptr ElgamalCipher -> Ptr ElgamalCipher -> EncryptedAmountAggIndex -> Word64 -> Ptr CChar -> IO a) ->
Expand Down Expand Up @@ -491,6 +500,14 @@ instance FromJSON SecToPubAmountTransferData where
stpatdProof <- v .: "proof"
return SecToPubAmountTransferData{..}

instance ToJSON SecToPubAmountTransferData where
toJSON SecToPubAmountTransferData{..} =
object
[ "remainingAmount" .= stpatdRemainingAmount,
"transferAmount" .= stpatdTransferAmount,
"index" .= stpatdIndex,
"proof" .= stpatdProof
]
withSecToPubAmountTransferData ::
SecToPubAmountTransferData ->
(Ptr ElgamalCipher -> Ptr ElgamalCipher -> Word64 -> EncryptedAmountAggIndex -> Word64 -> Ptr CChar -> IO a) ->
Expand Down
8 changes: 8 additions & 0 deletions haskell-src/Concordium/ID/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,7 @@ instance Serialize CredentialDeploymentInformation where
instance Eq CredentialDeploymentInformation where
cdi1 == cdi2 = cdiValues cdi1 == cdiValues cdi2

-- Implement `FromJSON` instance for `CredentialDeploymentInformation`.
instance FromJSON CredentialDeploymentInformation where
parseJSON = withObject "CredentialDeploymentInformation" $ \x -> do
cdiValues <- parseJSON (Object x)
Expand All @@ -797,6 +798,13 @@ instance FromJSON CredentialDeploymentInformation where
}
Left _ -> fail "\"proofs\" is not a valid base16 string."

-- Implement `ToJSON` instance for `CredentialDeploymentInformation`.
instance ToJSON CredentialDeploymentInformation where
toJSON CredentialDeploymentInformation{..} =
object $
("proofs" .= cdiProofs)
: credentialDeploymentValuesList cdiValues

-- | Information about the account that should be created as part of the initial
-- credential deployment.
data InitialCredentialAccount = InitialCredentialAccount
Expand Down
3 changes: 2 additions & 1 deletion haskell-src/Concordium/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ instance Show Address where

-- | Time in seconds since the unix epoch
newtype TransactionTime = TransactionTime {ttsSeconds :: Word64}
deriving (Show, Read, Eq, Num, Ord, FromJSON, ToJSON, Real, Enum, Integral) via Word64
deriving (Show, Read, Eq, Num, Ord, Real, FromJSON, ToJSON, Enum, Integral) via Word64

instance S.Serialize TransactionTime where
put = P.putWord64be . ttsSeconds
Expand Down Expand Up @@ -996,6 +996,7 @@ instance S.Serialize PayloadSize where
-- | Serialized payload of the transaction
newtype EncodedPayload = EncodedPayload {_spayload :: BSS.ShortByteString}
deriving (Eq, Show)
deriving (AE.ToJSON, AE.FromJSON) via BSH.ByteStringHex
DOBEN marked this conversation as resolved.
Show resolved Hide resolved

-- | There is no corresponding getter (to fit into the Serialize instance) since
-- encoded payload does not encode its own length. See 'getPayload' below.
Expand Down
23 changes: 23 additions & 0 deletions haskell-src/Concordium/Types/Execution.hs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import Concordium.Types.Execution.TH
import Concordium.Types.Updates
import Concordium.Utils
import qualified Concordium.Wasm as Wasm
import Data.Char (isLower)

-- | We assume that the list is non-empty and at most 255 elements long.
newtype AccountOwnershipProof = AccountOwnershipProof [(KeyIndex, Dlog25519Proof)]
Expand Down Expand Up @@ -159,6 +160,14 @@ bakerKeysWithProofsSize :: Int
bakerKeysWithProofsSize =
VRF.publicKeySize + dlogProofSize + Sig.publicKeySize + dlogProofSize + Bls.publicKeySize + Bls.proofSize

-- Implement `FromJSON` and `ToJSON` instances for `BakerKeysWithProofs`.
$( deriveJSON
defaultOptions
{ AE.fieldLabelModifier = firstLower . dropWhile isLower
}
''BakerKeysWithProofs
)

DOBEN marked this conversation as resolved.
Show resolved Hide resolved
-- | The transaction payload. Defines the supported kinds of transactions.
--
-- * @SPEC: <$DOCS/Transactions#transaction-body>
Expand Down Expand Up @@ -404,6 +413,20 @@ instance S.Serialize TransactionType where
20 -> return TTConfigureDelegation
n -> fail $ "Unrecognized TransactionType tag: " ++ show n

-- Implement `FromJSON` and `ToJSON` instances for `Payload`.
$( deriveJSON
defaultOptions
{ AE.constructorTagModifier = firstLower,
AE.fieldLabelModifier = firstLower . dropWhile isLower,
AE.sumEncoding =
AE.TaggedObject
{ AE.tagFieldName = "transactionType",
AE.contentsFieldName = ""
}
}
''Payload
)

-- | Payload serialization according to
--
-- * @SPEC: <$DOCS/Transactions#transaction-body>
Expand Down
85 changes: 81 additions & 4 deletions haskell-src/Concordium/Types/Transactions.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Data.Aeson (FromJSON (..), ToJSON (..))
import qualified Data.Aeson as AE
import Data.Aeson.TH
import qualified Data.ByteString as BS
import Data.Char (isLower)
import Data.List (foldl')
import qualified Data.Map.Strict as Map
import qualified Data.Serialize as S
Expand All @@ -30,6 +31,7 @@ import qualified Data.Vector as Vec

import Concordium.ID.Types
import Concordium.Types
import Concordium.Types.Execution
import Concordium.Types.HashableTo
import Concordium.Types.Updates
import Concordium.Utils
Expand Down Expand Up @@ -139,6 +141,75 @@ instance S.Serialize TransactionSignature where
accumulateSigs (Map.insert idx sigmap accum) (Just idx) (count - 1)
TransactionSignature <$> accumulateSigs Map.empty Nothing len

-----------------------------------------------------------------

-- * JSON representation of a signed/partially-signed transaction

-----------------------------------------------------------------

-- | A 'SignedTransaction' is a transaction that is signed by an account (the signer)
-- with some keys. The representation might be a fully signed transaction ready to be
-- sent on-chain or a partially-signed transaction that needs additional signatures
-- added to be ready to be sent on-chain.
--
-- The `ToJSON` instance has the purpose converting the object into a human-readable
-- representation ready to be printed into a JSON file. This file can be shared among
-- different tools of the Concordium ecosystem for adding additional signatures.
--
-- The chosen representation is the minimal necessary data needed to construct the
-- 'TransactionSignHash' which is the value that is signed by the signer. The
-- 'TransactionSignHash' and 'payloadSize' should be re-computed when processing a
-- 'SignedTransaction' (e.g. when adding signatures or sending the transaction on-chain).
data SignedTransaction = SignedTransaction
DOBEN marked this conversation as resolved.
Show resolved Hide resolved
{ -- | Amount of energy dedicated to the execution of this transaction.
stEnergy :: !Energy,
-- | Absolute expiration time after which transaction will not be executed.
stExpiryTime :: !TransactionExpiryTime,
-- | Account nonce.
stNonce :: !Nonce,
-- | Signer account address.
stSigner :: !AccountAddress,
-- | The payload of the transaction.
stPayload :: !Payload,
-- | Signatures generated by the signer account. This map might contain enough signatures to send the transaction on-chain or
-- additional signatures are needed before the transaction is considered fully signed.
stSignature :: !TransactionSignature
}
deriving (Eq, Show)

-- | Implement `FromJSON` and `ToJSON` instances for `SignedTransaction`.
instance ToJSON SignedTransaction where
toJSON SignedTransaction{..} =
AE.object
[ "version" AE..= signedTransactionVersion,
"energy" AE..= stEnergy,
"expiryTime" AE..= stExpiryTime,
"nonce" AE..= stNonce,
"signer" AE..= stSigner,
"payload" AE..= stPayload,
"signature" AE..= stSignature
]

-- Implement `FromJSON` instance for `SignedTransaction`.
instance FromJSON SignedTransaction where
parseJSON = AE.withObject "SignedTransaction" $ \obj -> do
stVersion <- obj AE..: "version"
if stVersion /= signedTransactionVersion
then fail $ "Unexpected version: " ++ show stVersion
else do
stEnergy <- obj AE..: "energy"
stExpiryTime <- obj AE..: "expiryTime"
stNonce <- obj AE..: "nonce"
stSigner <- obj AE..: "signer"
stSignature <- obj AE..: "signature"
stPayload <- obj AE..: "payload"
return SignedTransaction{..}

-- | The initial version of the above `SignedTransaction` JSON representation.
-- The version will be incremented when introducing a new format in the future.
signedTransactionVersion :: Int
signedTransactionVersion = 1

-- | An 'AccountTransaction' is a transaction that originates from
-- a specific account (the sender), and is paid for by the sender.
--
Expand All @@ -160,6 +231,14 @@ data AccountTransaction = AccountTransaction
}
deriving (Eq, Show)

-- Implement `FromJSON` and `ToJSON` instances for `AccountTransaction`.
$( deriveJSON
defaultOptions
{ AE.fieldLabelModifier = firstLower . dropWhile isLower
}
''AccountTransaction
)
DOBEN marked this conversation as resolved.
Show resolved Hide resolved

-- | Construct an 'AccountTransaction', computing the correct
-- 'TransactionSignHash'.
makeAccountTransaction :: TransactionSignature -> TransactionHeader -> EncodedPayload -> AccountTransaction
Expand Down Expand Up @@ -526,15 +605,13 @@ signTransactionSingle kp = signTransaction [(0, [(0, kp)])]
-- * @SPEC: <$DOCS/Transactions#transaction-signature>
signTransaction :: [(CredentialIndex, [(KeyIndex, KeyPair)])] -> TransactionHeader -> EncodedPayload -> AccountTransaction
signTransaction keys atrHeader atrPayload =
let
atrSignHash = transactionSignHashFromHeaderPayload atrHeader atrPayload
let atrSignHash = transactionSignHashFromHeaderPayload atrHeader atrPayload
-- only sign the hash of the transaction
bodyHash = transactionSignHashToByteString atrSignHash
credSignature cKeys = Map.fromList $ map (\(idx, key) -> (idx, SigScheme.sign key bodyHash)) cKeys
tsSignatures = Map.fromList $ map (\(idx, cKeys) -> (idx, credSignature cKeys)) keys
atrSignature = TransactionSignature{..}
in
AccountTransaction{..}
in AccountTransaction{..}

-- | Verify credential signatures. This checks
--
Expand Down
43 changes: 40 additions & 3 deletions haskell-src/Concordium/Wasm.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}

-- |
Expand Down Expand Up @@ -146,13 +147,14 @@ module Concordium.Wasm (

import Control.Monad
import qualified Data.Aeson as AE
import Data.Aeson.TH
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base16 as BS16
import Data.ByteString.Short (ShortByteString)
import qualified Data.ByteString.Short as BSS
import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
import Data.Char (isAlphaNum, isAscii, isPunctuation)
import Data.Char (isAlphaNum, isAscii, isLower, isPunctuation)
import qualified Data.HashMap.Strict as HM
import Data.Hashable
import Data.Int (Int32)
Expand All @@ -174,6 +176,7 @@ import qualified Concordium.Crypto.SHA256 as H
import Concordium.ID.Types
import Concordium.Types
import Concordium.Types.HashableTo
import Concordium.Utils
import Concordium.Utils.Serialization

--------------------------------------------------------------------------------
Expand All @@ -193,8 +196,8 @@ pvCostSemanticsVersion = \case
SP6 -> CSV0
SP7 -> CSV1

-- |Convert the version to a Word8. This is used when transferring information
-- via FFI.
-- | Convert the version to a Word8. This is used when transferring information
-- via FFI.
costSemanticsVersionToWord8 :: CostSemanticsVersion -> Word8
costSemanticsVersionToWord8 = \case
CSV0 -> 0
Expand Down Expand Up @@ -265,6 +268,17 @@ demoteWasmVersion SV1 = V1
newtype ModuleSource (v :: WasmVersion) = ModuleSource {moduleSource :: ByteString}
deriving (Eq, Show)

-- Implement `ToJSON` instance for `ModuleSource`.
instance AE.ToJSON (ModuleSource v) where
toJSON (ModuleSource v) = AE.String (Text.decodeUtf8 (BS16.encode v))

-- Implement `FromJSON` instance for `ModuleSource`.
instance AE.FromJSON (ModuleSource v) where
parseJSON = AE.withText "source" $ \t ->
case BS16.decode (Text.encodeUtf8 t) of
Right bs -> return $ ModuleSource bs
Left _ -> fail "Could not decode ModuleSource from JSON"

instance Serialize (ModuleSource V0) where
get = do
len <- getWord32be
Expand All @@ -290,6 +304,15 @@ moduleSourceLength = fromIntegral . BS.length . moduleSource
newtype WasmModuleV (v :: WasmVersion) = WasmModuleV {wmvSource :: ModuleSource v}
deriving (Eq, Show)

-- Implement `FromJSON` and `ToJSON` instances for `WasmModuleV`.
$( deriveJSON
defaultOptions
{ AE.constructorTagModifier = firstLower,
AE.fieldLabelModifier = firstLower . dropWhile isLower
}
''WasmModuleV
)

DOBEN marked this conversation as resolved.
Show resolved Hide resolved
instance (IsWasmVersion v) => Serialize (WasmModuleV v) where
put (WasmModuleV ws) = case getWasmVersion @v of
SV0 -> put V0 <> put ws
Expand All @@ -311,6 +334,20 @@ data WasmModule
| WasmModuleV1 (WasmModuleV V1)
deriving (Eq, Show)

-- Implement `FromJSON` and `ToJSON` instances for `WasmModule`.
$( deriveJSON
DOBEN marked this conversation as resolved.
Show resolved Hide resolved
defaultOptions
{ AE.constructorTagModifier = firstLower,
AE.fieldLabelModifier = firstLower . dropWhile isLower,
AE.sumEncoding =
AE.TaggedObject
{ AE.tagFieldName = "version",
AE.contentsFieldName = "content"
}
}
''WasmModule
)

getModuleRef :: forall v. (IsWasmVersion v) => WasmModuleV v -> ModuleRef
getModuleRef wm = case getWasmVersion @v of
SV0 -> ModuleRef (getHash wm)
Expand Down
2 changes: 2 additions & 0 deletions stack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ packages:
extra-lib-dirs:
- ./lib/

system-ghc: true
DOBEN marked this conversation as resolved.
Show resolved Hide resolved

extra-deps:
- proto-lens-setup-0.4.0.7@sha256:acca0b04e033ea0a017f809d91a7dbc942e025ec6bc275fa21647352722c74cc,3122
- proto-lens-protoc-0.8.0.0@sha256:a146ee8c9af9e445ab05651e688deb0ff849357d320657d6cea5be33cb54b960,2235
Expand Down
Loading