Skip to content

Commit

Permalink
feat: add lace support (PLT-5598)
Browse files Browse the repository at this point in the history
- modify Signature decodding based on how Lace is codding it
- for signature verification construct the Signed payload based on the Signature headers
- fix the skipped verification of the signature when the returned values is Right False
- add complete signature tests

refs: [PLT-5598](https://input-output.atlassian.net/browse/PLT-5598)
  • Loading branch information
bogdan-manole committed Sep 7, 2023
1 parent de1b18d commit 795be41
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,18 @@ import qualified Data.ByteString.Lazy as LBS

newtype Address = Address { unAddress :: ByteString } deriving (Show,Eq)
newtype Message = Message { unMessage :: ByteString } deriving (Show,Eq)
newtype Signature = Signature { unSignature :: ByteString } deriving (Show,Eq)
newtype Signature = Signature { unSignature :: ByteString } deriving (Eq)
newtype SignedMessage = SignedMessage { unSignedMessage :: ByteString } deriving (Show,Eq)
newtype PublicKey = PublicKey { unPublicKey :: ByteString } deriving (Show,Eq)
newtype PublicKey = PublicKey { unPublicKey :: ByteString } deriving (Eq)
newtype AddressHeader = AddressHeader { unAddressHeader :: ByteString } deriving (Show,Eq)

type ExpectedData = (Address,Message,Signature)
instance Show PublicKey where
show (PublicKey bs) = "PublicKey " ++ show (Hexa.encode bs)

instance Show Signature where
show (Signature bs) = "Signature " ++ show (Hexa.encode bs)

type ExpectedData = (Address,Message,Signature,AddressHeader)

data UnfoldedPayload = UnfoldedPayload
{ address :: Address
Expand All @@ -62,34 +69,44 @@ data UnfoldedPayload = UnfoldedPayload
unfoldPayload :: ByteString -> Either String UnfoldedPayload
unfoldPayload bs = case decodeCBORWith decodeRawSignature (LBS.fromStrict bs) of
Left err -> Left $ "unfoldPayload: " ++ show err
Right (address,message,signature) -> Right $ UnfoldedPayload
Right (address,message,signature,addressHeader) -> Right $ UnfoldedPayload
{ address
, message
, signature
, signedMessage = SignedMessage $ toStrictByteString $ encodeSignedMessage address message
, signedMessage = SignedMessage $ toStrictByteString $ encodeSignedMessage addressHeader message
}
{-
>>> (Right bs) = Hexa.decode "A3012704583900CA852E6C0A30AD556C9CAD808139E7D9BE715656B3302A84809DFA11DA43021ED7622C3A41B136FB61468015ACFF1D1AD34463617A2743EE6761646472657373583900CA852E6C0A30AD556C9CAD808139E7D9BE715656B3302A84809DFA11DA43021ED7622C3A41B136FB61468015ACFF1D1AD34463617A2743EE"
>>> decodeCBORWith decodeAddress (LBS.fromStrict bs)
Right "\NUL\202\133.l\n0\173Ul\156\173\128\129\&9\231\217\190qVV\179\&0*\132\128\157\250\DC1\218C\STX\RS\215b,:A\177\&6\251aF\128\NAK\172\255\GS\SUB\211Dcaz'C\238"
>>> (Right bs) = Hexa.decode "A201276761646472657373583900EEA6EEF941F733484373F7C274BE32BADB909980360EDEF283AD2B9C88D795F6A7C69398167BCF529865C8918A3DFE103653888E820B88FC"
>>> decodeCBORWith decodeAddress (LBS.fromStrict bs)
Right "\NUL\238\166\238\249A\247\&3HCs\247\194t\190\&2\186\219\144\153\128\&6\SO\222\242\131\173+\156\136\215\149\246\167\198\147\152\SYN{\207R\152e\200\145\138=\254\DLE6S\136\142\130\v\136\252"
-}
-- | Decode a CBOR-encoded expected data value.
decodeRawSignature :: Decoder s ExpectedData
decodeRawSignature = do
-- expect an array of 4 elements
len <- decodeListLen
unless (len == 4)
$ fail $ "decodeRawSignature: expected array of 4 elements, got " ++ show len
address <- decodeAddress'
(address,addressBs) <- decodeAddress'
-- ignore hashMap
_ <- decodeHashedMap
-- message
message <- decodeBytes
signature <- decodeBytes
pure (Address address,Message message,Signature signature)
pure (Address address,Message message,Signature signature, AddressHeader addressBs)
where
decodeAddress' = do
-- get bytes address
addressBs <- decodeBytes
case decodeCBORWith decodeAddress (LBS.fromStrict addressBs) of
Left err -> fail err
Right a -> pure a
Right a -> pure (a,addressBs)
decodeHashedMap = do
len <- decodeMapLen
unless (len == 1)
Expand All @@ -103,9 +120,13 @@ decodeRawSignature = do

decodeAddress :: Decoder s ByteString
decodeAddress = do
_ <- decodeMapLen
_ <- decodeInt
_ <- decodeInt
len <- decodeMapLen
unless (len == 3 || len == 2)
$ fail $ "decodeAddress: expected map of 2 or 3 elements, got " ++ show len
_ <- decodeInt >> decodeInt
when (len == 3) $ do -- Lace case
decodeInt >> decodeBytes >> pure ()

addressKey <- decodeString
unless ("address" == addressKey)
$ fail $ "decodeAddress: unexpected key " ++ show addressKey
Expand All @@ -116,30 +137,66 @@ decodeCBORWith decoder bs = case CBOR.deserialiseFromBytes decoder bs of
Left err -> Left $ show err
Right (_, payload) -> Right payload

encodeSignedMessage :: Address -> Message -> Encoding
encodeSignedMessage (Address address) (Message message) =
encodeSignedMessage :: AddressHeader -> Message -> Encoding
encodeSignedMessage (AddressHeader addressBs) (Message message) =
encodeListLen 4
<> encodeString "Signature1"
<> encodeBytes (toStrictByteString encodeAddress)
<> encodeBytes addressBs
<> encodeBytes empty
<> encodeBytes message
where
encodeAddress = encodeMapLen 2
<> encodeInt 1
<> encodeInt (-8)
<> encodeString "address"
<> encodeBytes address
--where
{-
encodeAddress = encodeMapLen 3
<> encodeInt 1
<> encodeInt (-8)
<> encodeInt 4
<> encodeBytes address
<> encodeString "address"
<> encodeBytes address
-- example of PublicKey with algorithm
-- {1: 1, 3: -8, -1: 6, -2: h'DDA5CE7572D07A0B86C9B9951B081B9B67DDBD16C4E1A7DF7607C7B069557009'}
-}

-- example of PublicKey with algorithm (NUMY)
-- {1: 1
-- , 3: -8
-- , -1: 6
-- , -2: h'D3BE7240CD3F131316A2489C609FF4D7A75732834BD120F34618AFC66A236F16'
-- }

-- example of PublicKey with algorithm (LACE)
-- {1: 1
-- , 2: h'00CA852E6C0A30AD556C9CAD808139E7D9BE715656B3302A84809DFA11DA43021ED7622C3A41B136FB61468015ACFF1D1AD34463617A2743EE'
-- , 3: -8
-- , -1: 6
-- , -2: h'D3BE7240CD3F131316A2489C609FF4D7A75732834BD120F34618AFC66A236F16'
-- }



{-
Numy case
>>> (Right bs) = Hexa.decode "A4010103272006215820D3BE7240CD3F131316A2489C609FF4D7A75732834BD120F34618AFC66A236F16"
>>> decodeCBORWith decodePublicKeyWithAlgorithm (LBS.fromStrict bs)
Right "\211\190r@\205?\DC3\DC3\SYN\162H\156`\159\244\215\167W2\131K\209 \243F\CAN\175\198j#o\SYN"
Lace case
>>> (Right bs) = Hexa.decode "A5010102583900CA852E6C0A30AD556C9CAD808139E7D9BE715656B3302A84809DFA11DA43021ED7622C3A41B136FB61468015ACFF1D1AD34463617A2743EE03272006215820D3BE7240CD3F131316A2489C609FF4D7A75732834BD120F34618AFC66A236F16"
>>> decodeCBORWith decodePublicKeyWithAlgorithm (LBS.fromStrict bs)
Right "\211\190r@\205?\DC3\DC3\SYN\162H\156`\159\244\215\167W2\131K\209 \243F\CAN\175\198j#o\SYN"
-}
decodePublicKeyWithAlgorithm :: Decoder s ByteString
decodePublicKeyWithAlgorithm = do
len <- decodeMapLen
unless (len == 4)
$ fail $ "decodePublicKeyWithAlgorithm: expected map of 4 elements, got " ++ show len
-- ignore 3 key-pairs and last key
replicateM_ 7 decodeInt
-- 4 for NUMY, 5 for LACE
unless (len == 4 || len == 5)
$ fail $ "decodePublicKeyWithAlgorithm: expected map of 4 or 5 elements, got " ++ show len
replicateM_ 2 decodeInt
when (len == 5) $ do -- Lace case
decodeInt >> decodeBytes >> pure ()
replicateM_ 5 decodeInt
decodeBytes

unfoldPublicKey :: ByteString -> Either String PublicKey
Expand Down
2 changes: 1 addition & 1 deletion nix/docker-files/docker.nix
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{ inputs', pkgs, l, ... }: let
imgAttributes = {
name = "plutus-certification";
tag = "13";
tag = "14";
};
nixConfig = ''
trusted-public-keys = hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= iohk.cachix.org-1:DpRUyj7h7V830dp/i6Nti+NEO2/nhblbov/8MW7Rqoo= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=
Expand Down
1 change: 1 addition & 0 deletions nix/docker-files/run.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#UNSAFE_PLAIN_ADDRESS_AUTH=1 \
NIX_CONFIG="system = x86_64-linux" \
WALLET_ADDRESS=addr_test1qphgqts20fhx0yx7ug42xehcnryukchy5k7hpaksgxax2fzt5w2gu33s8wrw3c9tjs97dr5pulsvf39e56v7c9ar39asptcrtp \
WALLET_ID=73857344a0cf884fe044abfe85660cc9a81f6366 \
WALLET_PASSPHRASE=test123456 \
Expand Down
2 changes: 2 additions & 0 deletions plutus-certification.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,7 @@ test-suite plutus-certification-test
, hspec
, plutus-certification
, dapps-certification-persistence
, dapps-certification-signature-verification
, text
, raw-strings-qq
, aeson
6 changes: 5 additions & 1 deletion src/Plutus/Certification/Server/Instance.hs
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,11 @@ server ServerArgs{..} = NamedAPI
eb = serverEventBackend
verifySignature key signature address =
let res = verifyCIP30Signature key signature Nothing (Just $ Bech32Address address)
in either (\err ->throwError err403 { errBody = LSB.pack err}) (const $ pure ()) res
in either (\err -> throwError err401 { errBody = LSB.pack err })
pure $ case res of
Left err -> Left err
Right False -> Left "Signature verification failed"
Right True -> Right ()

whenJWTProvided handler = case jwtArgs of
Nothing -> throwError err404
Expand Down
97 changes: 97 additions & 0 deletions test/SignatureSpec.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
{-# OPTIONS_GHC -Wno-missing-signatures #-}
{-# OPTIONS_GHC -Wno-unused-record-wildcards #-}

module SignatureSpec (spec) where
import Test.Hspec
import IOHK.Certification.SignatureVerification
import Data.Aeson
import Plutus.Certification.API
import Text.RawString.QQ(r)

(Just laceObject) = decode @LoginBody [r|{
"address": "addr_test1qr9g2tnvpgc264tvnjkcpqfeulvmuu2k26enq25yszwl5yw6gvppa4mz9sayrvfklds5dqq44nl36xkng33kz738g0hqj0adak",
"key": "a5010102583900ca852e6c0a30ad556c9cad808139e7d9be715656b3302a84809dfa11da43021ed7622c3a41b136fb61468015acff1d1ad34463617a2743ee03272006215820d3be7240cd3f131316a2489c609ff4d7a75732834bd120f34618afc66a236f16",
"signature": "845882a3012704583900ca852e6c0a30ad556c9cad808139e7d9be715656b3302a84809dfa11da43021ed7622c3a41b136fb61468015acff1d1ad34463617a2743ee6761646472657373583900ca852e6c0a30ad556c9cad808139e7d9be715656b3302a84809dfa11da43021ed7622c3a41b136fb61468015acff1d1ad34463617a2743eea166686173686564f458d45369676e2074686973206d65737361676520696620796f752061726520746865206f776e6572206f662074686520616464725f74657374317172396732746e7670676332363474766e6a6b6370716665756c766d7575326b3236656e71323579737a776c357977366776707061346d7a397361797276666b6c64733564717134346e6c3336786b6e6733336b7a373338673068716a306164616b20616464726573732e200a2054696d657374616d703a203c3c313639333935383634313e3e200a204578706972793a203630207365636f6e64735840511b7f3b847ad1e5b8ffac44ff4af5f5cbf1f4dda3d4d179dbc5c9ce1626aaca0118795fa41bdd0a4440473a609bfe47f6b4c23bd34b0a9bf8b7a56c6e120009"
}|]

(Just numyObject) = decode @LoginBody [r|{
"address": "addr_test1qr9g2tnvpgc264tvnjkcpqfeulvmuu2k26enq25yszwl5yw6gvppa4mz9sayrvfklds5dqq44nl36xkng33kz738g0hqj0adak",
"key": "a4010103272006215820d3be7240cd3f131316a2489c609ff4d7a75732834bd120f34618afc66a236f16",
"signature": "845846a201276761646472657373583900ca852e6c0a30ad556c9cad808139e7d9be715656b3302a84809dfa11da43021ed7622c3a41b136fb61468015acff1d1ad34463617a2743eea166686173686564f458d45369676e2074686973206d65737361676520696620796f752061726520746865206f776e6572206f662074686520616464725f74657374317172396732746e7670676332363474766e6a6b6370716665756c766d7575326b3236656e71323579737a776c357977366776707061346d7a397361797276666b6c64733564717134346e6c3336786b6e6733336b7a373338673068716a306164616b20616464726573732e200a2054696d657374616d703a203c3c313639333935373436313e3e200a204578706972793a203630207365636f6e64735840a2559f6b3aaed7dd920c8d647c118758fcf7d958279d0291271bfe77150c8cc3faac5367879236abbb325bd6361f06f5d59e8178feed8406ba3f67140abae504"
}|]

(Just yoroiObject) = decode @LoginBody [r|{
"address": "stake_test1urdyxqs76a3zcwjpkym0kc2xsq26elcartf5gcmp0gn58ms32qx04",
"key": "a4010103272006215820a1904e3efe35ad81a349f7d973943e3e340558ae9966b704f0ac6bddb4cc8c49",
"signature": "84582aa201276761646472657373581de0da43021ed7622c3a41b136fb61468015acff1d1ad34463617a2743eea166686173686564f458d45369676e2074686973206d65737361676520696620796f752061726520746865206f776e6572206f662074686520616464725f74657374317172303278346a726d7271756d36307765773672356638347671396c677a7a7437667634766a393271756c6d767737366776707061346d7a397361797276666b6c64733564717134346e6c3336786b6e6733336b7a37333867306871713836616b7220616464726573732e200a2054696d657374616d703a203c3c313639343038303837383e3e200a204578706972793a203630207365636f6e6473584094b50f5393283596c7c2d85a1d6f6d8a31be87b79c31ffcb4185191a070ce2374d6b1900d0632b78a419dbc9937f97716d14750f9881b2d1d44df5ffbac75508"
}|]

(Just wrongAddressObject) = decode @LoginBody [r|{
"address": "addr_test1qr9g2tnvpgc264tvnjkcpqfeulvmuu2k26enq25yszwl5yw6gvppa4mz9sayrvfklds5dqq44nl36xkng33kz738g0hqj0adak",
"key": "a4010103272006215820a1904e3efe35ad81a349f7d973943e3e340558ae9966b704f0ac6bddb4cc8c49",
"signature": "84582aa201276761646472657373581de0da43021ed7622c3a41b136fb61468015acff1d1ad34463617a2743eea166686173686564f458d45369676e2074686973206d65737361676520696620796f752061726520746865206f776e6572206f662074686520616464725f74657374317172303278346a726d7271756d36307765773672356638347671396c677a7a7437667634766a393271756c6d767737366776707061346d7a397361797276666b6c64733564717134346e6c3336786b6e6733336b7a37333867306871713836616b7220616464726573732e200a2054696d657374616d703a203c3c313639343038303837383e3e200a204578706972793a203630207365636f6e6473584094b50f5393283596c7c2d85a1d6f6d8a31be87b79c31ffcb4185191a070ce2374d6b1900d0632b78a419dbc9937f97716d14750f9881b2d1d44df5ffbac75508"
}|]

(Just wrongPublicKey) = decode @LoginBody [r|{
"address": "stake_test1urdyxqs76a3zcwjpkym0kc2xsq26elcartf5gcmp0gn58ms32qx04",
"key": "a5010102583900ca852e6c0a30ad556c9cad808139e7d9be715656b3302a84809dfa11da43021ed7622c3a41b136fb61468015acff1d1ad34463617a2743ee03272006215820d3be7240cd3f131316a2489c609ff4d7a75732834bd120f34618afc66a236f16",
"signature": "84582aa201276761646472657373581de0da43021ed7622c3a41b136fb61468015acff1d1ad34463617a2743eea166686173686564f458d45369676e2074686973206d65737361676520696620796f752061726520746865206f776e6572206f662074686520616464725f74657374317172303278346a726d7271756d36307765773672356638347671396c677a7a7437667634766a393271756c6d767737366776707061346d7a397361797276666b6c64733564717134346e6c3336786b6e6733336b7a37333867306871713836616b7220616464726573732e200a2054696d657374616d703a203c3c313639343038303837383e3e200a204578706972793a203630207365636f6e6473584094b50f5393283596c7c2d85a1d6f6d8a31be87b79c31ffcb4185191a070ce2374d6b1900d0632b78a419dbc9937f97716d14750f9881b2d1d44df5ffbac75508"

}|]

(Just malformedPublicKey) = decode @LoginBody [r|{
"address": "stake_test1urdyxqs76a3zcwjpkym0kc2xsq26elcartf5gcmp0gn58ms32qx04",
"key": "A6010102583900CA852E6C0A30AD556C9CAD808139E7D9BE715656B3302A84809DFA11DA43021ED7622C3A41B136FB61468015ACFF1D1AD34463617A2743EE032720060405215820D3BE7240CD3F131316A2489C609FF4D7A75732834BD120F34618AFC66A236F16",
"signature": "84582aa201276761646472657373581de0da43021ed7622c3a41b136fb61468015acff1d1ad34463617a2743eea166686173686564f458d45369676e2074686973206d65737361676520696620796f752061726520746865206f776e6572206f662074686520616464725f74657374317172303278346a726d7271756d36307765773672356638347671396c677a7a7437667634766a393271756c6d767737366776707061346d7a397361797276666b6c64733564717134346e6c3336786b6e6733336b7a37333867306871713836616b7220616464726573732e200a2054696d657374616d703a203c3c313639343038303837383e3e200a204578706972793a203630207365636f6e6473584094b50f5393283596c7c2d85a1d6f6d8a31be87b79c31ffcb4185191a070ce2374d6b1900d0632b78a419dbc9937f97716d14750f9881b2d1d44df5ffbac75508"
}|]

(Just wrongSignature) = decode @LoginBody [r|{
"address": "stake_test1urdyxqs76a3zcwjpkym0kc2xsq26elcartf5gcmp0gn58ms32qx04",
"key": "a4010103272006215820a1904e3efe35ad81a349f7d973943e3e340558ae9966b704f0ac6bddb4cc8c49",
"signature": "84582AA201276761646472657373581DE0DA43021ED7622C3A41B136FB61468015ACFF1D1AD34463617A2743EEA166686173686564F458D45369676E2074686973206D65737361676520696620796F752061726520746865206F776E6572206F662074686520616464725F74657374317172396732746E7670676332363474766E6A6B6370716665756C766D7575326B3236656E71323579737A776C357977366776707061346D7A397361797276666B6C64733564717134346E6C3336786B6E6733336B7A373338673068716A306164616B20616464726573732E200A2054696D657374616D703A203C3C313639333935373436313E3E200A204578706972793A203630207365636F6E64735840A2559F6B3AAED7DD920C8D647C118758FCF7D958279D0291271BFE77150C8CC3FAAC5367879236ABBB325BD6361F06F5D59E8178FEED8406BA3F67140ABAE504"
}|]
spec :: SpecWith ()
spec =
describe "Signature verification" $ do

it "successfully validates Lace signature" $ do
let LoginBody{..} = laceObject
verifyCIP30Signature key signature Nothing (Just $ Bech32Address address) `shouldBe` Right True

it "successfully validates Numy signature" $ do
let LoginBody{..} = numyObject
verifyCIP30Signature key signature Nothing (Just $ Bech32Address address) `shouldBe` Right True

it "successfully validates Yoroi signature" $ do
let LoginBody{..} = yoroiObject
verifyCIP30Signature key signature Nothing (Just $ Bech32Address address) `shouldBe` Right True

it "fails to validate signature with wrong address" $ do
let LoginBody{..} = wrongAddressObject
verifyCIP30Signature key signature Nothing (Just $ Bech32Address address)
`shouldBe` Left "Hash address verification failed"

it "fails to validate signature with wrong public key" $ do
let LoginBody{..} = wrongPublicKey
verifyCIP30Signature key signature Nothing (Just $ Bech32Address address)
`shouldBe` Left "Hash address verification failed"

it "fails to validate signature with malformed public key" $ do
let LoginBody{..} = malformedPublicKey
verifyCIP30Signature key signature Nothing (Just $ Bech32Address address)
`shouldBe` Left
"unfoldPublicKey: \"DeserialiseFailure 1 \\\"decodePublicKeyWithAlgorithm: expected map of 4 or 5 elements, got 6\\\"\""

it "fails to validate signature with wrong signature" $ do
let LoginBody{..} = wrongSignature
verifyCIP30Signature key signature Nothing (Just $ Bech32Address address)
`shouldBe` Right False


5 changes: 4 additions & 1 deletion test/Spec.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import Test.Hspec
import qualified ProfileWalletSpec as ProfileWallet
import qualified SignatureSpec as Signature

main :: IO ()
main = hspec ProfileWallet.spec
main = hspec $ do
ProfileWallet.spec
Signature.spec

0 comments on commit 795be41

Please sign in to comment.