mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-04 10:47:32 +00:00
192 lines
12 KiB
Haskell
192 lines
12 KiB
Haskell
{-# LANGUAGE NamedFieldPuns #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
module Bots.StoreService.NamesSignatureTests (namesSignatureTests) where
|
|
|
|
import Control.Monad (forM_)
|
|
import Data.ByteArray.Encoding (Base (Base16), convertFromBase)
|
|
import Data.ByteString (ByteString)
|
|
import qualified Data.ByteString as B
|
|
import Data.Maybe (fromMaybe)
|
|
import Data.Text (Text)
|
|
import qualified Data.Text as T
|
|
import Data.Text.Encoding (encodeUtf8)
|
|
import Data.Word (Word64)
|
|
import Simplex.Messaging.Crypto.Secp256k1 (RecoverableSignature (..))
|
|
import Simplex.Messaging.Encoding.String (strDecode)
|
|
import Simplex.Messaging.Eth.Address (Address, recoverAddress)
|
|
import Simplex.Messaging.SimplexName (SimplexTLD (..), labelHash)
|
|
import StoreService.Names.Abi
|
|
import StoreService.Names.Phases (RegistrationId (..))
|
|
import StoreService.Names.Registration
|
|
import StoreService.Names.Signatures
|
|
import Test.Hspec
|
|
|
|
namesSignatureTests :: Spec
|
|
namesSignatureTests = describe "StoreService names: signatures" $ do
|
|
-- expected values are viem 2.57.5's for the fixtures below, the webapp's wallet (BIP-39 entropy 6f2d1c9a4b7e3f80d15a2c6e9b04f7a3, account 0)
|
|
-- and the SimplexSponsor, SimplexController, SimplexResolver and SimplexPriceOracle ABIs of ens-contracts PR #36 at 23892cc
|
|
describe "EIP-712" $ do
|
|
it "commits to a registration with one contact link" $
|
|
makeCommitment dynamis `shouldBe` commitment
|
|
it "hashes RegisterName" $
|
|
registerIntentDigest (sponsorDomain chainId sponsor) dynamis 0 1800000000 `shouldBe` hx "0x4b0bcc282db581446f6c25e0e3fbe2839409acbc011bb3e082523dd9eab85a6a"
|
|
it "hashes SetText" $
|
|
setTextDigest (resolverDomain chainId resolver) (namehash TLDSimplex "dynamis") "simplex.contact" "https://x" 3 99 `shouldBe` hx "0xa62d4320889eed3713462d725dc298cd96885572b8e6b424e93a205a96b96fb5"
|
|
it "hashes ClearRecords" $
|
|
clearRecordsDigest (resolverDomain chainId resolver) (namehash TLDSimplex "dynamis") 4 99 `shouldBe` hx "0xca4bd243288f27f38e3b45384ba36ce685ff155352cf8125f031e6fb189a46dd"
|
|
describe "EIP-191 messages" $ do
|
|
it "signs an order" $
|
|
testMessage
|
|
(orderMessage dynamisOrder timestamp)
|
|
"simplex.domains order 0x78b602f03f164366531da963764c1f2fdfd588d0487daf2aa261ca799e5ffe33 7 69259500 0x9cf04ddef2eedfb02e6e7ba58f879c0636c584b6acc67ffe9f97a06bfb016ac1 0xb1971beb567c8dc1b2f01f0d823754c6168d8d6830db2221702a7678e1804674 1792574100"
|
|
"0x5deee1b3589df9e53665e34aa65a491a4eabfd76a39207b0e9303457f7c44b7a"
|
|
"0x73e32be461e99e64b093a4ad09b04b4d810e5623a1b0e9c9fdb4701ec2a6a2a84df9ffdccc39c5cb827cde4d846c7662f591665f1b868ab1c15778646d3f414e1b"
|
|
it "signs find with the owner in lowercase" $
|
|
testMessage
|
|
(findMessage owner timestamp)
|
|
"simplex.domains find 0x014f9c09cceedef1b713110d04d3a07d5b5d9b73 1792574100"
|
|
"0x08a989fbf0457984c062b44d7237be9c7948f2cf23d48410609777a09b515646"
|
|
"0xfa0908260c3f21c8e47bd37e2c3cf3bd6bc62966591ab1a2c4e31695646548f47d2e79b34413be27b4b07330682de642f48a8f5e641449d7d79728eeb4ab14da1c"
|
|
it "signs another name for an order, with its duration" $
|
|
testMessage
|
|
(retargetMessage registrationId dynamisOrder {ofLabelHash = labelHash "another"} timestamp)
|
|
"simplex.domains name t2Jk8RmQ4vXw9NcP3yLb6g 0x5bfbbf75311ec002834031d73fa30bfe8bdf675f4a3df72443841490c4b49d02 7 69259500 0x9cf04ddef2eedfb02e6e7ba58f879c0636c584b6acc67ffe9f97a06bfb016ac1 0xb1971beb567c8dc1b2f01f0d823754c6168d8d6830db2221702a7678e1804674 1792574100"
|
|
"0x8bc79aa98fbd53013e8961db27554ecd17139e14ec168b86afbaf2fc910bfc52"
|
|
"0xe3d680bcc62255179d5260beeb920319c86bd919f6b01927bee01509cf6c1fe3121151947fb9e3a310e6652c0c9a9bf7ceb48e8d903774a38a3162d916e8b4591b"
|
|
it "signs a taken order's code, with its length" $
|
|
testMessage
|
|
(takenCodeMessage registrationId "7hg6ED6EotRVm21uS1KOZ-CKEzkJK5hvG4W5atfcn_Q" 7 timestamp)
|
|
"simplex.domains code t2Jk8RmQ4vXw9NcP3yLb6g 7hg6ED6EotRVm21uS1KOZ-CKEzkJK5hvG4W5atfcn_Q 7 1792574100"
|
|
"0x1f61297cd585c7c94218f8f87f30ea20e9b0631d36890460f24ee9bca83a2a19"
|
|
"0x897173292eb63ac3587d3c900af08dee71f296af8718ec045dd49ce953bb0f666eefe546bea0d3623022c7cc7f03cc572a498bb0ceac26bb46ed37c9e99ecdb51b"
|
|
describe "signature format" $ do
|
|
it "refuses a high s, though recovery accepts it" testHighS
|
|
it "refuses v other than 27 or 28" $
|
|
forM_ ["00", "01", "1d"] $ \v -> parseSignature (T.dropEnd 2 orderSignature <> v) `shouldBe` Left SERecoveryId
|
|
it "refuses uppercase hex, no 0x and a wrong length" $ do
|
|
parseSignature ("0x" <> T.toUpper (T.drop 2 orderSignature)) `shouldBe` Left SEFormat
|
|
parseSignature (T.drop 2 orderSignature) `shouldBe` Left SEFormat
|
|
parseSignature (T.dropEnd 2 orderSignature) `shouldBe` Left SEFormat
|
|
parseSignature (orderSignature <> "00") `shouldBe` Left SEFormat
|
|
describe "addresses" $ do
|
|
it "accepts the EIP-55 form in orders" $
|
|
parseChecksumAddress ownerText `shouldBe` Right owner
|
|
it "refuses a letter in the wrong case and one-case hex in orders" $ do
|
|
parseChecksumAddress "0x014F9c09CcEeDEF1B713110d04D3a07d5B5D9b73" `shouldBe` Left ADChecksum
|
|
parseChecksumAddress ownerLower `shouldBe` Left ADChecksum
|
|
parseChecksumAddress ownerUpper `shouldBe` Left ADChecksum
|
|
it "accepts any case in find" $
|
|
forM_ [ownerText, ownerLower, ownerUpper, "0x014F9c09CcEeDEF1B713110d04D3a07d5B5D9b73"] $ \a ->
|
|
parseAnyCaseAddress a `shouldBe` Right owner
|
|
it "refuses what is not an address" $
|
|
forM_ ["014f9c09cceedef1b713110d04d3a07d5b5d9b73", "0X014f9c09cceedef1b713110d04d3a07d5b5d9b73", T.dropEnd 1 ownerLower, ownerLower <> "0", "0x014f9c09cceedef1b713110d04d3a07d5b5d9b7g"] $ \a -> do
|
|
parseAnyCaseAddress a `shouldBe` Left ADFormat
|
|
parseChecksumAddress a `shouldBe` Left ADFormat
|
|
describe "ABI return data" $ do
|
|
it "decodes each type as viem encodes it" $ do
|
|
decodeUint256 (hx "00000000000000000000000000000000000000018ee90ff6c373e0ee4e3f0ad2") `shouldBe` Right 123456789012345678901234567890
|
|
decodeAddress (hx "000000000000000000000000014f9c09cceedef1b713110d04d3a07d5b5d9b73") `shouldBe` Right owner
|
|
decodeBool (hx "0000000000000000000000000000000000000000000000000000000000000001") `shouldBe` Right True
|
|
decodeBool (B.replicate 32 0) `shouldBe` Right False
|
|
decodeBytes32 (hx "833cc73a7eae1ddee67ec8845ec3d39799406c7bc87fab7c504a7e0ab5cb89bb") `shouldBe` Right node
|
|
decodeString retString `shouldBe` Right "https://smp8.simplex.im/a#contact"
|
|
decodeString (hx "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000") `shouldBe` Right ""
|
|
decodeBytes (hx "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000010410f13a8c833cc73a7eae1ddee67ec8845ec3d39799406c7bc87fab7c504a7e0ab5cb89bb000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000f73696d706c65782e636f6e746163740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002168747470733a2f2f736d70382e73696d706c65782e696d2f6123636f6e746163740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") `shouldBe` Right (hx "10f13a8c833cc73a7eae1ddee67ec8845ec3d39799406c7bc87fab7c504a7e0ab5cb89bb000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000f73696d706c65782e636f6e746163740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002168747470733a2f2f736d70382e73696d706c65782e696d2f6123636f6e7461637400000000000000000000000000000000000000000000000000000000000000")
|
|
it "decodes the oracle's prices" $ do
|
|
decodePrices retPrices `shouldBe` Right (5000, [(7, 10000), (6, 20000)])
|
|
decodePrices (hx "000000000000000000000000000000000000000000000000000000000000138800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000") `shouldBe` Right (5000, [])
|
|
it "refuses data that does not hold the type" $ do
|
|
decodeUint256 (B.replicate 31 0) `shouldBe` Left AEShort
|
|
decodeAddress (B.singleton 1 <> B.drop 1 (hx "000000000000000000000000014f9c09cceedef1b713110d04d3a07d5b5d9b73")) `shouldBe` Left AENotAddress
|
|
decodeBool (uint256 (2 :: Int)) `shouldBe` Left AENotBool
|
|
decodeString (uint256 (4096 :: Int) <> B.drop 32 retString) `shouldBe` Left AEOffset
|
|
decodeString (B.take 80 retString) `shouldBe` Left AEShort
|
|
decodeString (abiTuple [Dynamic $ abiBytes "\xff"]) `shouldBe` Left AENotUtf8
|
|
decodePrices (B.dropEnd 32 retPrices) `shouldBe` Left AEShort
|
|
|
|
testMessage :: ByteString -> ByteString -> Text -> Text -> IO ()
|
|
testMessage msg expected digest signature = do
|
|
msg `shouldBe` expected
|
|
personalDigest msg `shouldBe` hx digest
|
|
sig <- either (fail . show) pure $ parseSignature signature
|
|
recoverAddress sig (personalDigest msg) `shouldReturn` Right owner
|
|
|
|
testHighS :: IO ()
|
|
testHighS = do
|
|
parseSignature highS `shouldBe` Left SEHighS
|
|
let (rsR, (rsS, v)) = B.splitAt 32 <$> B.splitAt 32 (hx highS)
|
|
recoverAddress RecoverableSignature {rsR, rsS, rsRecId = B.head v - 27} (personalDigest $ orderMessage dynamisOrder timestamp)
|
|
`shouldReturn` Right owner
|
|
where
|
|
highS = "0x73e32be461e99e64b093a4ad09b04b4d810e5623a1b0e9c9fdb4701ec2a6a2a8b206002333c63a347d8321b27b93899bc51d768793c21589fe7ae62862f6fff31c"
|
|
|
|
-- fixtures
|
|
|
|
dynamis :: Registration
|
|
dynamis =
|
|
Registration
|
|
{ rLabel = "dynamis",
|
|
rTLD = TLDSimplex,
|
|
rOwner = owner,
|
|
rDuration = duration,
|
|
rSecret = B.replicate 32 0x11,
|
|
rResolver = resolver,
|
|
rContactLink = Just "https://smp8.simplex.im/a#contact",
|
|
rChannelLink = Nothing
|
|
}
|
|
|
|
dynamisOrder :: OrderFields
|
|
dynamisOrder =
|
|
OrderFields
|
|
{ ofLabelHash = labelHash "dynamis",
|
|
ofLength = 7,
|
|
ofDuration = duration,
|
|
ofCommitment = commitment,
|
|
ofSealed = "q3Zk0Yx7c2VhbGVkLW9yZGVyLWZpeHR1cmU"
|
|
}
|
|
|
|
commitment :: ByteString
|
|
commitment = hx "0x9cf04ddef2eedfb02e6e7ba58f879c0636c584b6acc67ffe9f97a06bfb016ac1"
|
|
|
|
node :: ByteString
|
|
node = hx "0x833cc73a7eae1ddee67ec8845ec3d39799406c7bc87fab7c504a7e0ab5cb89bb"
|
|
|
|
-- 21 October 2026 09:15 UTC
|
|
timestamp :: Word64
|
|
timestamp = 1792574100
|
|
|
|
-- 2 years of 365 days and the free days until 1 January 2027
|
|
duration :: Word64
|
|
duration = 69259500
|
|
|
|
registrationId :: RegistrationId
|
|
registrationId = RegistrationId "t2Jk8RmQ4vXw9NcP3yLb6g"
|
|
|
|
orderSignature :: Text
|
|
orderSignature = "0x73e32be461e99e64b093a4ad09b04b4d810e5623a1b0e9c9fdb4701ec2a6a2a84df9ffdccc39c5cb827cde4d846c7662f591665f1b868ab1c15778646d3f414e1b"
|
|
|
|
retString :: ByteString
|
|
retString = hx "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002168747470733a2f2f736d70382e73696d706c65782e696d2f6123636f6e7461637400000000000000000000000000000000000000000000000000000000000000"
|
|
|
|
retPrices :: ByteString
|
|
retPrices = hx "0000000000000000000000000000000000000000000000000000000000001388000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000007000000000000000000000000000000000000000000000000000000000000271000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000004e20"
|
|
|
|
owner :: Address
|
|
owner = either error id $ strDecode $ encodeUtf8 ownerText
|
|
|
|
ownerText, ownerLower, ownerUpper :: Text
|
|
ownerText = "0x014f9c09CcEeDEF1B713110d04D3a07d5B5D9b73"
|
|
ownerLower = T.toLower ownerText
|
|
ownerUpper = "0x" <> T.toUpper (T.drop 2 ownerText)
|
|
|
|
sponsor, resolver :: Address
|
|
sponsor = either error id $ strDecode "0x9a676e781a523b5d0c0e43731313a708cb607508"
|
|
resolver = either error id $ strDecode "0x2279b7a0a67db372996a5fab50d91eaa73d2ebe6"
|
|
|
|
chainId :: Word64
|
|
chainId = 31337
|
|
|
|
hx :: Text -> ByteString
|
|
hx t = either error id $ convertFromBase Base16 $ encodeUtf8 $ fromMaybe t $ T.stripPrefix "0x" t
|