mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-11-02 04:42:25 +00:00
store: sign and store the agreed terms
This commit is contained in:
1 parent
de991bc53c
commit
45302749f5
11 files changed
+196
-87
No files matched your search
@@ -47,6 +47,7 @@ import qualified Data.Aeson as J
|
||||
import qualified Data.Aeson.KeyMap as KM
|
||||
import Data.Bifunctor (first)
|
||||
import Data.ByteString (ByteString)
|
||||
import Data.Char (isAsciiLower, isAsciiUpper, isDigit)
|
||||
import Data.Int (Int64)
|
||||
import Data.Maybe (catMaybes, isJust)
|
||||
import Data.Scientific (toBoundedInteger)
|
||||
@@ -129,6 +130,10 @@ data Landing = LNotLanded | LRegistered Int64 | LChanged | LUnread
|
||||
maxSealedChars :: Int
|
||||
maxSealedChars = 11095
|
||||
|
||||
-- | The terms version, of up to 32 characters, and a suffix such as /community.
|
||||
maxAgreedTermsChars :: Int
|
||||
maxAgreedTermsChars = 48
|
||||
|
||||
-- | How far a signed request's timestamp may be from the service's clock.
|
||||
signedRequestSeconds :: Integer
|
||||
signedRequestSeconds = 600
|
||||
@@ -149,9 +154,16 @@ parseOrderFields o = do
|
||||
ofCommitment <- bytes32FromHex =<< textField o "commitment"
|
||||
ofSealed <- textField o "sealed"
|
||||
unless (T.length ofSealed <= maxSealedChars) Nothing
|
||||
ofAgreedTerms <- textField o "terms"
|
||||
unless (agreedTermsFormat ofAgreedTerms) Nothing
|
||||
timestamp <- integerField =<< KM.lookup "timestamp" o
|
||||
signature <- either (const Nothing) Just . parseSignature =<< textField o "signature"
|
||||
pure (OrderFields {ofLabelHash, ofLength, ofDuration, ofCommitment, ofSealed}, timestamp, signature)
|
||||
pure (OrderFields {ofLabelHash, ofLength, ofDuration, ofCommitment, ofSealed, ofAgreedTerms}, timestamp, signature)
|
||||
|
||||
agreedTermsFormat :: Text -> Bool
|
||||
agreedTermsFormat t = not (T.null t) && T.length t <= maxAgreedTermsChars && T.all termsChar t
|
||||
where
|
||||
termsChar c = isAsciiUpper c || isAsciiLower c || isDigit c || c `elem` ("._/-" :: String)
|
||||
|
||||
textField :: J.Object -> J.Key -> Maybe Text
|
||||
textField o k = case KM.lookup k o of
|
||||
@@ -284,8 +296,8 @@ movable NameRegistration {nrPhase, nrLinksRefusedAt} = case nrPhase of
|
||||
_ -> False
|
||||
|
||||
orderFieldsOf :: NameRegistration -> OrderFields
|
||||
orderFieldsOf NameRegistration {nrLabelHash, nrLength, nrDuration, nrCommitment, nrSealed} =
|
||||
OrderFields {ofLabelHash = nrLabelHash, ofLength = nrLength, ofDuration = nrDuration, ofCommitment = nrCommitment, ofSealed = nrSealed}
|
||||
orderFieldsOf NameRegistration {nrLabelHash, nrLength, nrDuration, nrCommitment, nrSealed, nrAgreedTerms} =
|
||||
OrderFields {ofLabelHash = nrLabelHash, ofLength = nrLength, ofDuration = nrDuration, ofCommitment = nrCommitment, ofSealed = nrSealed, ofAgreedTerms = nrAgreedTerms}
|
||||
|
||||
-- | Any other live order of the new commitment, or of the owner and the new label hash, refuses the move.
|
||||
checkMoveMeets :: DBStore -> NameRegistration -> OrderFields -> UTCTime -> IO (Either OrderRefusal ())
|
||||
|
||||
@@ -49,13 +49,14 @@ data AddressError
|
||||
| ADChecksum
|
||||
deriving (Eq, Show)
|
||||
|
||||
-- | The fields an owner signs in an order and in another name for it.
|
||||
-- | The fields an owner signs in an order and in another name for it, with the terms the owner agreed to for that name.
|
||||
data OrderFields = OrderFields
|
||||
{ ofLabelHash :: LabelHash,
|
||||
ofLength :: Int,
|
||||
ofDuration :: Word64,
|
||||
ofCommitment :: ByteString,
|
||||
ofSealed :: Text
|
||||
ofSealed :: Text,
|
||||
ofAgreedTerms :: Text
|
||||
}
|
||||
deriving (Eq)
|
||||
|
||||
@@ -108,8 +109,8 @@ message :: [Text] -> ByteString
|
||||
message = encodeUtf8 . T.unwords . ("simplex.domains" :)
|
||||
|
||||
orderWords :: OrderFields -> [Text]
|
||||
orderWords OrderFields {ofLabelHash = LabelHash labelHash, ofLength, ofDuration, ofCommitment, ofSealed} =
|
||||
[hex labelHash, tshow ofLength, tshow ofDuration, hex ofCommitment, hex $ sha256Hash $ encodeUtf8 ofSealed]
|
||||
orderWords OrderFields {ofLabelHash = LabelHash labelHash, ofLength, ofDuration, ofCommitment, ofSealed, ofAgreedTerms} =
|
||||
[hex labelHash, tshow ofLength, tshow ofDuration, hex ofCommitment, hex $ sha256Hash $ encodeUtf8 ofSealed, ofAgreedTerms]
|
||||
|
||||
-- the address parser checks EIP-55 only on mixed-case hex, so one-case text is refused here by comparing the encoding
|
||||
parseChecksumAddress :: Text -> Either AddressError Address
|
||||
|
||||
@@ -195,6 +195,8 @@ data NameRegistration = NameRegistration
|
||||
nrOwner :: Address,
|
||||
nrCommitment :: ByteString,
|
||||
nrSealed :: Text,
|
||||
-- | '' for an order stored before orders signed the terms
|
||||
nrAgreedTerms :: Text,
|
||||
nrPhase :: NamePhase,
|
||||
nrYears :: Int,
|
||||
nrPrice :: Int,
|
||||
@@ -476,8 +478,8 @@ orderHashFieldBytes = 4
|
||||
|
||||
-- | Each field is length-prefixed, so no two different orders encode the same.
|
||||
orderHash :: Address -> OrderFields -> OrderTerms -> ByteString
|
||||
orderHash owner OrderFields {ofLabelHash = LabelHash lh, ofLength, ofDuration, ofCommitment, ofSealed} terms =
|
||||
sha256Hash . B.concat . map field $ [tag, lh, num ofLength, num ofDuration, unAddress owner, ofCommitment, encodeUtf8 ofSealed] <> payment
|
||||
orderHash owner OrderFields {ofLabelHash = LabelHash lh, ofLength, ofDuration, ofCommitment, ofSealed, ofAgreedTerms} terms =
|
||||
sha256Hash . B.concat . map field $ [tag, lh, num ofLength, num ofDuration, unAddress owner, ofCommitment, encodeUtf8 ofSealed, encodeUtf8 ofAgreedTerms] <> payment
|
||||
where
|
||||
(tag, payment) = case terms of
|
||||
OTInvoice method price -> ("name-invoice", [encodeUtf8 $ methodText method, num price])
|
||||
@@ -557,15 +559,16 @@ insertRegistration db NewRegistration {nrgId = RegistrationId rid, nrgOwner, nrg
|
||||
[sql|
|
||||
INSERT INTO sx_badge_service_name_registrations
|
||||
(registration_id, version, label_hash, length, duration, owner, commitment, sealed, phase, years,
|
||||
price, method, name_prepaid_code_id, live, order_hash, moves, created_at, updated_at, balance_source, balance_used)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?, ?,?,?,?,?,?,?,?,?,?)
|
||||
price, method, name_prepaid_code_id, live, order_hash, moves, created_at, updated_at, balance_source, balance_used, agreed_terms)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?, ?,?,?,?,?,?,?,?,?,?, ?)
|
||||
ON CONFLICT DO NOTHING
|
||||
|]
|
||||
( (rid, initialVersion, Binary lh, ofLength, int64 ofDuration, Binary $ unAddress nrgOwner, Binary ofCommitment, ofSealed, phase, nrgYears)
|
||||
:. (price, methodText <$> method, codeId, BI True, Binary hash, 0 :: Int, now, now, (\(RegistrationId s, _) -> s) <$> fromBalance, maybe 0 snd fromBalance)
|
||||
:. Only ofAgreedTerms
|
||||
)
|
||||
where
|
||||
OrderFields {ofLabelHash = LabelHash lh, ofLength, ofDuration, ofCommitment, ofSealed} = nrgOrder
|
||||
OrderFields {ofLabelHash = LabelHash lh, ofLength, ofDuration, ofCommitment, ofSealed, ofAgreedTerms} = nrgOrder
|
||||
|
||||
registrationColumns :: Query
|
||||
registrationColumns =
|
||||
@@ -573,7 +576,8 @@ registrationColumns =
|
||||
SELECT r.registration_id, r.version, r.label_hash, r.length, r.duration, r.owner, r.commitment, r.sealed, r.phase, r.years,
|
||||
r.price, r.method, ni.invoice_id, r.name_prepaid_code_id, r.label, r.secret, r.contact_link, r.channel_link, r.intent_nonce, r.intent_deadline,
|
||||
r.intent_signature, r.commit_tx, r.commit_block, r.register_tx, r.register_block, r.reveal_after, r.reveal_before, r.expires_at, r.registered_at,
|
||||
r.failed_at, r.error, r.live, r.order_hash, r.moves, r.links_refused_at, r.created_at, r.updated_at, r.balance_source, r.balance_used
|
||||
r.failed_at, r.error, r.live, r.order_hash, r.moves, r.links_refused_at, r.created_at, r.updated_at, r.balance_source, r.balance_used,
|
||||
r.agreed_terms
|
||||
|]
|
||||
|
||||
registrationFrom :: Query
|
||||
@@ -592,6 +596,7 @@ type RegistrationRow =
|
||||
:. (Int, Maybe Text, Maybe Text, Maybe Int64, Maybe Text, Maybe (Binary ByteString), Maybe Text, Maybe Text, Maybe Int64, Maybe Int64)
|
||||
:. (Maybe (Binary ByteString), Maybe (Binary ByteString), Maybe Int64, Maybe (Binary ByteString), Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime)
|
||||
:. (Maybe UTCTime, Maybe Text, BoolInt, Binary ByteString, Int, Maybe UTCTime, UTCTime, UTCTime, Maybe Text, Int)
|
||||
:. Only Text
|
||||
|
||||
toRegistration :: RegistrationRow -> Either Text NameRegistration
|
||||
toRegistration
|
||||
@@ -599,6 +604,7 @@ toRegistration
|
||||
:. (nrPrice, method_, invoiceId_, codeId_, label_, secret_, nrContactLink, nrChannelLink, nonce_, deadline_)
|
||||
:. (signature_, commitTx, commitBlock, registerTx, registerBlock, revealAfter, revealBefore, expiresAt, nrRegisteredAt)
|
||||
:. (nrFailedAt, nrError, BI nrLive, Binary nrOrderHash, nrMoves, nrLinksRefusedAt, nrCreatedAt, nrUpdatedAt, source_, nrBalanceUsed)
|
||||
:. Only nrAgreedTerms
|
||||
) = do
|
||||
nrOwner <- decodeAddress owner
|
||||
nrPayment <- case (method_, invoiceId_, codeId_) of
|
||||
@@ -622,6 +628,7 @@ toRegistration
|
||||
nrOwner,
|
||||
nrCommitment,
|
||||
nrSealed,
|
||||
nrAgreedTerms,
|
||||
nrPhase,
|
||||
nrYears,
|
||||
nrPrice,
|
||||
@@ -734,7 +741,7 @@ updateRegistration db r now = do
|
||||
db
|
||||
[sql|
|
||||
UPDATE sx_badge_service_name_registrations
|
||||
SET version = version + 1, label_hash = ?, length = ?, duration = ?, commitment = ?, sealed = ?, phase = ?, price = ?,
|
||||
SET version = version + 1, label_hash = ?, length = ?, duration = ?, commitment = ?, sealed = ?, agreed_terms = ?, phase = ?, price = ?,
|
||||
label = ?, secret = ?, contact_link = ?, channel_link = ?, intent_nonce = ?, intent_deadline = ?, intent_signature = ?,
|
||||
commit_tx = ?, commit_block = ?, register_tx = ?, register_block = ?, reveal_after = ?, reveal_before = ?, expires_at = ?,
|
||||
registered_at = ?, failed_at = ?, error = ?, live = ?, moves = ?, links_refused_at = ?, updated_at = ?
|
||||
@@ -744,7 +751,7 @@ updateRegistration db r now = do
|
||||
WHERE o.live = 1 AND o.registration_id <> ? AND (o.commitment = ? OR (o.owner = ? AND o.label_hash = ?))
|
||||
)))
|
||||
|]
|
||||
( (Binary lh, nrLength, int64 nrDuration, Binary nrCommitment, nrSealed, nrPhase, nrPrice)
|
||||
( (Binary lh, nrLength, int64 nrDuration, Binary nrCommitment, nrSealed, nrAgreedTerms, nrPhase, nrPrice)
|
||||
:. (rvLabel <$> nrReveal, Binary . rvSecret <$> nrReveal, rvContactLink =<< nrReveal, rvChannelLink =<< nrReveal, int64 . rvNonce <$> nrReveal, int64 . rvDeadline <$> nrReveal, Binary . serializeSignature . rvSignature <$> nrReveal)
|
||||
:. (txHashField <$> nrCommitTx, int64 <$> nrCommitBlock, txHashField <$> nrRegisterTx, int64 <$> nrRegisterBlock, int64 <$> nrRevealAfter, int64 <$> nrRevealBefore, int64 <$> nrExpiresAt)
|
||||
:. (truncateToSecond <$> nrRegisteredAt, truncateToSecond <$> nrFailedAt, nrError, BI live, nrMoves, truncateToSecond <$> nrLinksRefusedAt, truncateToSecond now)
|
||||
@@ -752,7 +759,7 @@ updateRegistration db r now = do
|
||||
)
|
||||
if changed > 0 then pure $ Written (nrVersion + 1) else refusal
|
||||
where
|
||||
NameRegistration {nrId = RegistrationId rid, nrVersion, nrLabelHash = LabelHash lh, nrLength, nrDuration, nrOwner, nrCommitment, nrSealed, nrPhase, nrPrice, nrReveal} = r
|
||||
NameRegistration {nrId = RegistrationId rid, nrVersion, nrLabelHash = LabelHash lh, nrLength, nrDuration, nrOwner, nrCommitment, nrSealed, nrAgreedTerms, nrPhase, nrPrice, nrReveal} = r
|
||||
NameRegistration {nrCommitTx, nrCommitBlock, nrRegisterTx, nrRegisterBlock, nrRevealAfter, nrRevealBefore, nrExpiresAt} = r
|
||||
NameRegistration {nrRegisteredAt, nrFailedAt, nrError, nrMoves, nrLinksRefusedAt} = r
|
||||
live = orderLive r
|
||||
@@ -761,10 +768,10 @@ updateRegistration db r now = do
|
||||
[(version, BI storedLive)] | version == nrVersion -> pure $ if storedLive then LiveOrderExists else NotLive
|
||||
_ -> pure VersionChanged
|
||||
|
||||
-- | Another name, or other links: queued again with the reveal, transactions, reveal times and link refusal cleared.
|
||||
-- | Another name, or other links, with the terms agreed for it: queued again with the reveal, transactions, reveal times and link refusal cleared.
|
||||
-- The order hash stays the original order's, so a resend of that order still finds it.
|
||||
retargetRegistration :: DB.Connection -> NameRegistration -> OrderFields -> Int -> UTCTime -> IO VersionWrite
|
||||
retargetRegistration db r OrderFields {ofLabelHash, ofLength, ofDuration, ofCommitment, ofSealed} price =
|
||||
retargetRegistration db r OrderFields {ofLabelHash, ofLength, ofDuration, ofCommitment, ofSealed, ofAgreedTerms} price =
|
||||
updateRegistration
|
||||
db
|
||||
r
|
||||
@@ -773,6 +780,7 @@ retargetRegistration db r OrderFields {ofLabelHash, ofLength, ofDuration, ofComm
|
||||
nrDuration = ofDuration,
|
||||
nrCommitment = ofCommitment,
|
||||
nrSealed = ofSealed,
|
||||
nrAgreedTerms = ofAgreedTerms,
|
||||
nrPrice = price,
|
||||
nrPhase = PQueued,
|
||||
nrMoves = nrMoves r + 1,
|
||||
|
||||
@@ -24,7 +24,7 @@ import StoreService.Service (ServiceState (..), checkDevChain, newServiceState,
|
||||
import StoreService.Store (ManagedGroup (..), getManagedGroup)
|
||||
import StoreService.Waiters (newWaiters)
|
||||
import Bots.StoreService.BotTests (badgeBotName, badgeCmdUsage, badgeProfile, badgeTypeOf, credentialOf, devKeysIni, entryOf, issueCode, issueRaw, mkStoreServiceOpts, newPurchaseKeys, operatorCmd, redeemWithQueue, requestObject, revokeRaw, serviceDbPrefix, shouldAnswerError, statementOf, stopStoreService, testIssuerKeyIdx, withStoreService)
|
||||
import Bots.StoreService.ConfigTests (chainSection, controllerAddr, namesSection)
|
||||
import Bots.StoreService.ConfigTests (chainSection, controllerAddr, namesSection, termsVersion)
|
||||
import Bots.StoreService.FakeEthNode (CallAnswer (..), FakeEthNode (..), Fault (..), answerCall, awaitSent, setFault, withFakeEthNode)
|
||||
import Bots.StoreService.WebTests (capturingStderr, digestFixture)
|
||||
import Bots.StoreService.NamesSearchTests (aliceHex)
|
||||
@@ -220,6 +220,7 @@ testNamesBoot ps = do
|
||||
"sealed" J..= ("sealed" :: Text),
|
||||
"price" J..= (10000 :: Int),
|
||||
"balance" J..= (10000 :: Int),
|
||||
"terms" J..= termsVersion,
|
||||
"timestamp" J..= (1792574100 :: Int),
|
||||
"signature" J..= ("0x" <> T.replicate 32 "11" <> T.replicate 32 "22" <> "1b")
|
||||
]
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
module Bots.StoreService.NamesBalancesTests (namesBalancesTests, cardPaid, coveredSpend, credit, creditLate, fieldsN, figures, holdPrice, holder, invariantHolds, lateCredit, payment, placeOrderRow, settleHolder, source) where
|
||||
|
||||
import Bots.StoreService.ConfigTests (termsVersion)
|
||||
import Bots.StoreService.NamesOrdersTests (Ctx (..), withOrders)
|
||||
import Bots.StoreService.NamesStoreTests (at, btc, invoiceStatus, newReg, ownerA, ownerB, paidOrder, reg, signature, t0, write)
|
||||
import Bots.StoreService.WebTests (capturingStderr, rcv, withServiceStore, withServiceStores)
|
||||
@@ -101,7 +102,8 @@ fieldsN label =
|
||||
ofLength = T.length label,
|
||||
ofDuration = 69292800,
|
||||
ofCommitment = C.sha256Hash $ encodeUtf8 label,
|
||||
ofSealed = "sealed-" <> label
|
||||
ofSealed = "sealed-" <> label,
|
||||
ofAgreedTerms = termsVersion
|
||||
}
|
||||
|
||||
-- | A crypto invoice row, or a card one with its client secret in place of an address.
|
||||
|
||||
@@ -10,6 +10,8 @@ module Bots.StoreService.NamesOrdersTests
|
||||
withOrdersVia,
|
||||
withOrdersOn,
|
||||
withOrdersCancelling,
|
||||
withSettings,
|
||||
configured,
|
||||
aliceOrder,
|
||||
commitmentOf,
|
||||
fieldsOf,
|
||||
@@ -34,6 +36,7 @@ module Bots.StoreService.NamesOrdersTests
|
||||
field,
|
||||
advance,
|
||||
refusedWith,
|
||||
malformedTerms,
|
||||
)
|
||||
where
|
||||
|
||||
@@ -226,6 +229,7 @@ data Order = Order
|
||||
oDuration :: Word64,
|
||||
oMethod :: Text,
|
||||
oPrice :: Int,
|
||||
oTerms :: Text,
|
||||
oTimestamp :: Word64
|
||||
}
|
||||
|
||||
@@ -240,6 +244,7 @@ aliceOrder =
|
||||
oDuration = durationAt orderTime,
|
||||
oMethod = "btc",
|
||||
oPrice = alicePrice,
|
||||
oTerms = termsVersion,
|
||||
oTimestamp = seconds orderTime
|
||||
}
|
||||
|
||||
@@ -251,8 +256,8 @@ commitmentOf :: Int -> ByteString
|
||||
commitmentOf n = C.sha256Hash $ BC.pack $ "commitment " <> show n
|
||||
|
||||
fieldsOf :: Order -> OrderFields
|
||||
fieldsOf Order {oLabel, oCommitment, oSealed, oDuration} =
|
||||
OrderFields {ofLabelHash = labelHash oLabel, ofLength = T.length oLabel, ofDuration = oDuration, ofCommitment = oCommitment, ofSealed = oSealed}
|
||||
fieldsOf Order {oLabel, oCommitment, oSealed, oDuration, oTerms} =
|
||||
OrderFields {ofLabelHash = labelHash oLabel, ofLength = T.length oLabel, ofDuration = oDuration, ofCommitment = oCommitment, ofSealed = oSealed, ofAgreedTerms = oTerms}
|
||||
|
||||
signedBody :: TVar ChaChaDRG -> Order -> IO J.Object
|
||||
signedBody g o@Order {oKey, oTimestamp} = do
|
||||
@@ -271,10 +276,11 @@ unsignedBody o@Order {oOwner, oMethod, oPrice, oTimestamp} =
|
||||
("sealed", J.String ofSealed),
|
||||
("method", J.String oMethod),
|
||||
("price", J.toJSON oPrice),
|
||||
("terms", J.String ofAgreedTerms),
|
||||
("timestamp", J.toJSON oTimestamp)
|
||||
]
|
||||
where
|
||||
OrderFields {ofLabelHash = LabelHash lh, ofLength, ofDuration, ofCommitment, ofSealed} = fieldsOf o
|
||||
OrderFields {ofLabelHash = LabelHash lh, ofLength, ofDuration, ofCommitment, ofSealed, ofAgreedTerms} = fieldsOf o
|
||||
|
||||
withSignature :: Text -> J.Object -> J.Object
|
||||
withSignature sig = KM.insert "signature" (J.String sig)
|
||||
@@ -352,6 +358,20 @@ withOrdersOn cOpenStore via action = cOpenStore >>= \cStore -> withFakeEthNode $
|
||||
Warp.testWithApplicationSettings (webSettings (listener cfg)) (pure $ ordersApp cWeb cOrders) $ \port ->
|
||||
action Ctx {cStore, cOpenStore, cNode, cOrders, cWeb, cClient = (mgr, "http://127.0.0.1:" <> show port), cStub, cClock, cRandom, cIps}
|
||||
|
||||
-- | The context's store, chain, providers and clock with its names settings changed, as after a restart.
|
||||
configured :: (NamesConfig -> NamesConfig) -> Ctx -> Ctx
|
||||
configured change ctx@Ctx {cOrders, cWeb} = ctx {cOrders = orders, cWeb = cWeb {weOrders = Just orders}}
|
||||
where
|
||||
orders = cOrders {oeConfig = change (oeConfig cOrders)}
|
||||
|
||||
-- | Another server of the orders routes over the context with its names settings changed.
|
||||
withSettings :: (NamesConfig -> NamesConfig) -> Ctx -> (Ctx -> IO a) -> IO a
|
||||
withSettings change ctx action = do
|
||||
let ctx'@Ctx {cOrders, cWeb} = configured change ctx
|
||||
mgr <- newManager defaultManagerSettings
|
||||
Warp.testWithApplicationSettings (webSettings (listener $ weConfig cWeb)) (pure $ ordersApp cWeb cOrders) $ \port ->
|
||||
action ctx' {cClient = (mgr, "http://127.0.0.1:" <> show port)}
|
||||
|
||||
ordersApp :: WebEnv -> OrdersEnv -> Application
|
||||
ordersApp web orders req respond = fromMaybe (webApp web req respond) (namesApp (weHttp web) (codeRoutes orders (weProviders web) (weWaiters web)) req respond)
|
||||
|
||||
@@ -463,24 +483,35 @@ testPaidOrder = withOrders $ \ctx -> do
|
||||
reg <- invoiceRegistration ctx invId
|
||||
(nrPhase reg, nrLive reg, nrVersion reg, nrYears reg, nrPrice reg, nrPayment reg) `shouldBe` (PUnpaid, True, 1, 2, 10000, RPInvoice (SPMCrypto CCBtc) invId)
|
||||
nrOrderHash reg `shouldBe` orderHash ownerA (fieldsOf aliceOrder) (OTInvoice (SPMCrypto CCBtc) 10000)
|
||||
nrAgreedTerms reg `shouldBe` termsVersion
|
||||
|
||||
-- | The body of the webapp's order vector, with viem's signature over it (NamesSignatureTests).
|
||||
-- | The body of the contract's Appendix C order vector, with viem's signature over it (NamesSignatureTests).
|
||||
testViemOrder :: IO ()
|
||||
testViemOrder = withOrders $ \ctx -> do
|
||||
let dynamis =
|
||||
aliceOrder
|
||||
{ oLabel = "dynamis",
|
||||
oCommitment = unhex "9cf04ddef2eedfb02e6e7ba58f879c0636c584b6acc67ffe9f97a06bfb016ac1",
|
||||
oSealed = "q3Zk0Yx7c2VhbGVkLW9yZGVyLWZpeHR1cmU",
|
||||
oDuration = 69259500,
|
||||
oPrice = 20000
|
||||
}
|
||||
viemSignature = "0x73e32be461e99e64b093a4ad09b04b4d810e5623a1b0e9c9fdb4701ec2a6a2a84df9ffdccc39c5cb827cde4d846c7662f591665f1b868ab1c15778646d3f414e1b"
|
||||
signedBody (cRandom ctx) dynamis >>= (`shouldBe` withSignature viemSignature (unsignedBody dynamis))
|
||||
(status, v) <- postOrder ctx $ withSignature viemSignature (unsignedBody dynamis)
|
||||
testViemOrder = withOrders $ \ctx0 -> withSettings vectorTermStart ctx0 $ \ctx -> do
|
||||
writeIORef (cClock ctx) (posixSecondsToUTCTime $ fromIntegral $ oTimestamp vectorOrder)
|
||||
signedBody (cRandom ctx) vectorOrder >>= (`shouldBe` withSignature vectorSignature (unsignedBody vectorOrder))
|
||||
(status, v) <- postOrder ctx $ withSignature vectorSignature (unsignedBody vectorOrder)
|
||||
status `shouldBe` 200
|
||||
(field "phase" . registrationOf =<< v) `shouldBe` Just "unpaid"
|
||||
|
||||
-- | Appendix C's order: alicebakery with no links, signed with the terms version.
|
||||
vectorOrder :: Order
|
||||
vectorOrder =
|
||||
aliceOrder
|
||||
{ oCommitment = unhex "5e62a4cb7da596cb38169a6a552b5f69db2acee279b53be6693a78e03c3dfaa5",
|
||||
oSealed = "AAAAAAAAAAAAAAAACWgxqMk-UKfpra_Zh6vc4KNBR6SX1WYpGb4COoWCCbM0bisYCH14WLzghp8hqi_-KyXSi4qf_DAdP_M4bFetD0s",
|
||||
oDuration = 69292800,
|
||||
oTimestamp = 1792828800
|
||||
}
|
||||
|
||||
vectorSignature :: Text
|
||||
vectorSignature = "0x17107b11494e25098efc348aca64a2469ffb832bcc79dc57377a1b26a4004b6701ad73648f50af8b39d4079820a3de7e681ddf15f8cf15b5cff02b4a48cf1fe61c"
|
||||
|
||||
-- | Appendix C's duration is the page's at 21 October 2026 00:00 UTC, before its timestamp,
|
||||
-- so the term starts later by as much for the vector to pass the duration check at its timestamp.
|
||||
vectorTermStart :: NamesConfig -> NamesConfig
|
||||
vectorTermStart cfg = cfg {nTermStart = posixSecondsToUTCTime $ fromIntegral $ oTimestamp vectorOrder + oDuration vectorOrder - 2 * yearSeconds}
|
||||
|
||||
testExactResend :: IO ()
|
||||
testExactResend = withOrders $ \ctx -> do
|
||||
body <- signedBody (cRandom ctx) aliceOrder
|
||||
@@ -545,7 +576,8 @@ testOneFieldDiffers = withOrders $ \ctx -> do
|
||||
aliceOrder {oDuration = oDuration aliceOrder + 1},
|
||||
aliceOrder {oMethod = "xmr"},
|
||||
aliceOrder {oMethod = "card"},
|
||||
aliceOrder {oPrice = alicePrice + 1}
|
||||
aliceOrder {oPrice = alicePrice + 1},
|
||||
aliceOrder {oTerms = termsVersion <> "/community"}
|
||||
]
|
||||
forM_ variants $ \o -> placeOrder ctx o `shouldReturn` refusedWith 409 "order_exists"
|
||||
length <$> stubCalls (cStub ctx) `shouldReturn` 1
|
||||
@@ -695,13 +727,20 @@ testFormats = withOrders $ \ctx -> do
|
||||
with "duration" (J.Number (-1)),
|
||||
with "timestamp" "1792574100",
|
||||
with "length" (J.Number 1e30),
|
||||
KM.delete "terms" good,
|
||||
with "terms" (J.Number 20261023),
|
||||
KM.delete "kind" good,
|
||||
with "kind" "badge"
|
||||
]
|
||||
$ \body -> postOrder ctx body `shouldReturn` refusedWith 400 "bad_request"
|
||||
forM_ malformedTerms $ \t -> placeOrder ctx aliceOrder {oTerms = t} `shouldReturn` refusedWith 400 "bad_request"
|
||||
forM_ [5, 64] $ \len -> postOrder ctx (with "length" (J.Number len)) `shouldReturn` refusedWith 400 "invalid_name"
|
||||
stubCalls (cStub ctx) `shouldReturn` []
|
||||
|
||||
-- | Signed as sent, so only their format refuses them.
|
||||
malformedTerms :: [Text]
|
||||
malformedTerms = ["", T.replicate 49 "a", "2026-10-23 community", "2026-10-23+community", "2026-10-23/communit\233"]
|
||||
|
||||
testTwoWallets :: IO ()
|
||||
testTwoWallets = withOrders $ \ctx -> do
|
||||
(a, _) <- placed ctx aliceOrder
|
||||
@@ -757,7 +796,7 @@ testCodeOrder = withOrders $ \ctx -> do
|
||||
rid <- registered ctx code aliceOrder
|
||||
r <- registrationById ctx rid
|
||||
(nrPhase r, nrLive r, nrVersion r, nrYears r, nrPrice r, nrPayment r) `shouldBe` (PQueued, True, 1, 2, 0, RPCode pcId)
|
||||
(nrOwner r, nrDuration r, nrCommitment r, nrSealed r) `shouldBe` (ownerA, durationAt orderTime, oCommitment aliceOrder, oSealed aliceOrder)
|
||||
(nrOwner r, nrDuration r, nrCommitment r, nrSealed r, nrAgreedTerms r) `shouldBe` (ownerA, durationAt orderTime, oCommitment aliceOrder, oSealed aliceOrder, termsVersion)
|
||||
nrOrderHash r `shouldBe` orderHash ownerA (fieldsOf aliceOrder) (OTCode pcCodeHash)
|
||||
held <- readCode ctx issued
|
||||
(pcReservedBy held, pcReservedAt held, pcSpentAt held) `shouldBe` (Just rid, Just orderTime, Nothing)
|
||||
@@ -791,6 +830,7 @@ testCodeOneFieldDiffers = withOrders $ \ctx -> do
|
||||
(code, aliceOrder {oCommitment = commitmentOf 2}),
|
||||
(code, aliceOrder {oSealed = "sealed-alicebakery-2"}),
|
||||
(code, aliceOrder {oDuration = oDuration aliceOrder + 1}),
|
||||
(code, aliceOrder {oTerms = termsVersion <> "/community"}),
|
||||
(other, aliceOrder)
|
||||
]
|
||||
forM_ variants $ \(c, o) -> register ctx c o `shouldReturn` refusedWith 409 "order_exists"
|
||||
@@ -922,9 +962,11 @@ testCodeSizes = withOrders $ \ctx -> do
|
||||
with "code" (J.Number 8),
|
||||
with "labelHash" (J.String $ T.toUpper $ hex $ (\(LabelHash h) -> h) alice),
|
||||
with "owner" (J.String $ T.toLower ownerAText),
|
||||
with "timestamp" "1792574100"
|
||||
with "timestamp" "1792574100",
|
||||
KM.delete "terms" good
|
||||
]
|
||||
$ \body -> postRegister ctx body `shouldReturn` refusedWith 400 "bad_request"
|
||||
forM_ malformedTerms $ \t -> register ctx code aliceOrder {oTerms = t} `shouldReturn` refusedWith 400 "bad_request"
|
||||
forM_ ["[]", "{"] $ \body -> post ctx "/api/name/register" body `shouldReturn` (400, errorOnly "bad_request")
|
||||
-- the formats and the length come before the code
|
||||
forM_ [5, 64] $ \len -> postRegister ctx (KM.insert "code" "not a code" $ with "length" (J.Number len)) `shouldReturn` refusedWith 400 "invalid_name"
|
||||
|
||||
@@ -28,6 +28,7 @@ module Bots.StoreService.NamesOwnerTests
|
||||
)
|
||||
where
|
||||
|
||||
import Bots.StoreService.ConfigTests (termsVersion)
|
||||
import Bots.StoreService.FakeEthNode
|
||||
import Bots.StoreService.NamesContractsTests (registrarOf, unhex)
|
||||
import Bots.StoreService.NamesOrdersTests
|
||||
@@ -88,6 +89,7 @@ namesOwnerTests = describe "StoreService names: owner" $ do
|
||||
it "refuses a move from registered, queued, taken, and committed without a refused reveal" testMoveRefusedPhases
|
||||
it "moves a failed order, a lapsed one, and a committed one whose links were refused, to the same name with other links too" testMoveAccepted
|
||||
it "answers a body equal to the current fields with the view unchanged, counting no move, whatever its timestamp" testMoveRepeat
|
||||
it "moves an order by a body differing only in terms, storing them" testMoveTermsOnly
|
||||
it "answers the fourth move move_limit with the current view, changing nothing" testMoveLimit
|
||||
it "refuses a move meeting another live order of the wallet's or the commitment with a bare order_exists, before the price" testMoveMeetsOrder
|
||||
it "runs the order checks on the new fields: invalid_name, the duration, name_taken, name_check_unavailable" testMoveOrderChecks
|
||||
@@ -238,13 +240,13 @@ linksRefusedPhase r = (committedPhase r) {nrLinksRefusedAt = Just orderTime}
|
||||
moveTo :: Ctx -> Text -> Int -> IO OrderFields
|
||||
moveTo Ctx {cClock} label n = do
|
||||
t <- readIORef cClock
|
||||
pure OrderFields {ofLabelHash = labelHash label, ofLength = T.length label, ofDuration = yearsDuration 2 t, ofCommitment = commitmentOf n, ofSealed = "sealed-" <> label <> "-" <> tshow n}
|
||||
pure OrderFields {ofLabelHash = labelHash label, ofLength = T.length label, ofDuration = yearsDuration 2 t, ofCommitment = commitmentOf n, ofSealed = "sealed-" <> label <> "-" <> tshow n, ofAgreedTerms = termsVersion}
|
||||
|
||||
moveBody :: Ctx -> Secp256k1PrivateKey -> RegistrationId -> OrderFields -> IO J.Object
|
||||
moveBody ctx key rid fields = clockSeconds ctx >>= moveBodyAt ctx key rid fields
|
||||
|
||||
moveBodyAt :: Ctx -> Secp256k1PrivateKey -> RegistrationId -> OrderFields -> Word64 -> IO J.Object
|
||||
moveBodyAt ctx key rid fields@OrderFields {ofLabelHash = LabelHash lh, ofLength, ofDuration, ofCommitment, ofSealed} timestamp = do
|
||||
moveBodyAt ctx key rid fields@OrderFields {ofLabelHash = LabelHash lh, ofLength, ofDuration, ofCommitment, ofSealed, ofAgreedTerms} timestamp = do
|
||||
sig <- signWith ctx key $ retargetMessage rid fields timestamp
|
||||
pure $
|
||||
KM.fromList
|
||||
@@ -253,6 +255,7 @@ moveBodyAt ctx key rid fields@OrderFields {ofLabelHash = LabelHash lh, ofLength,
|
||||
("duration", J.toJSON ofDuration),
|
||||
("commitment", J.String $ hex ofCommitment),
|
||||
("sealed", J.String ofSealed),
|
||||
("terms", J.String ofAgreedTerms),
|
||||
("timestamp", J.toJSON timestamp),
|
||||
("signature", J.String sig)
|
||||
]
|
||||
@@ -285,8 +288,8 @@ postFind :: Ctx -> J.Object -> IO (Int, Maybe J.Object)
|
||||
postFind ctx = post ctx "/api/registration/find" . J.encode
|
||||
|
||||
currentFields :: NameRegistration -> OrderFields
|
||||
currentFields NameRegistration {nrLabelHash, nrLength, nrDuration, nrCommitment, nrSealed} =
|
||||
OrderFields {ofLabelHash = nrLabelHash, ofLength = nrLength, ofDuration = nrDuration, ofCommitment = nrCommitment, ofSealed = nrSealed}
|
||||
currentFields NameRegistration {nrLabelHash, nrLength, nrDuration, nrCommitment, nrSealed, nrAgreedTerms} =
|
||||
OrderFields {ofLabelHash = nrLabelHash, ofLength = nrLength, ofDuration = nrDuration, ofCommitment = nrCommitment, ofSealed = nrSealed, ofAgreedTerms = nrAgreedTerms}
|
||||
|
||||
fieldsIn :: J.Object -> [(Text, J.Value)] -> Expectation
|
||||
fieldsIn v = mapM_ (\(k, value) -> (k, field k v) `shouldBe` (k, Just value))
|
||||
@@ -459,6 +462,13 @@ testMoveRepeat = withOwner $ \ctx -> do
|
||||
stored ctx r `shouldReturn` movedOnce
|
||||
nrMoves movedOnce `shouldBe` 1
|
||||
|
||||
testMoveTermsOnly :: IO ()
|
||||
testMoveTermsOnly = withOwner $ \ctx -> do
|
||||
r <- paidIn ctx aliceOrder $ \p -> (failedPhase p) {nrAgreedTerms = "2026-10-01"}
|
||||
(status, v) <- move ctx keyA r (currentFields r) {ofAgreedTerms = termsVersion}
|
||||
(status, field "phase" =<< v, field "moves" =<< v) `shouldBe` (200, Just "queued", Just (J.Number 1))
|
||||
(nrAgreedTerms &&& nrMoves) <$> stored ctx r `shouldReturn` (termsVersion, 1)
|
||||
|
||||
testMoveLimit :: IO ()
|
||||
testMoveLimit = withOwner $ \ctx -> do
|
||||
answerFree ctx "alicebakers"
|
||||
@@ -603,7 +613,7 @@ testMoveKeepsFromBalance = withOwner $ \ctx@Ctx {cStore} -> do
|
||||
answerFree ctx "alicebakers"
|
||||
paying <- paidIn ctx (byB aliceOrder) failedPhase
|
||||
withTransaction cStore $ \db -> creditBalance db (nrId paying) 5000 orderTime
|
||||
let fields = OrderFields {ofLabelHash = labelHash "dynamis", ofLength = 7, ofDuration = yearsDuration 2 orderTime, ofCommitment = commitmentOf 3, ofSealed = "sealed-dynamis-3"}
|
||||
let fields = OrderFields {ofLabelHash = labelHash "dynamis", ofLength = 7, ofDuration = yearsDuration 2 orderTime, ofCommitment = commitmentOf 3, ofSealed = "sealed-dynamis-3", ofAgreedTerms = termsVersion}
|
||||
new = NewRegistration {nrgId = RegistrationId "diff", nrgOwner = ownerA, nrgOrder = fields, nrgYears = 2}
|
||||
withTransaction cStore $ \db -> do
|
||||
insertInvoiceRow db "inv-diff" (addUTCTime 3600 orderTime) "open"
|
||||
@@ -667,8 +677,9 @@ testMoveSizes = withOwner $ \ctx -> do
|
||||
postMove ctx (nrId r) (KM.insert "owner" (J.String $ T.replicate 5300 "0") body) `shouldReturn` refusedWith 400 "bad_request"
|
||||
move ctx keyA r to {ofSealed = T.replicate 11096 "s"} `shouldReturn` refusedWith 400 "bad_request"
|
||||
valid <- moveBody ctx keyA (nrId r) to
|
||||
forM_ [KM.insert "labelHash" (J.String $ T.toUpper $ hex $ (\(LabelHash h) -> h) $ ofLabelHash to) valid, KM.delete "signature" valid, KM.insert "length" "11" valid] $ \malformed ->
|
||||
forM_ [KM.insert "labelHash" (J.String $ T.toUpper $ hex $ (\(LabelHash h) -> h) $ ofLabelHash to) valid, KM.delete "signature" valid, KM.insert "length" "11" valid, KM.delete "terms" valid] $ \malformed ->
|
||||
postMove ctx (nrId r) malformed `shouldReturn` refusedWith 400 "bad_request"
|
||||
forM_ malformedTerms $ \t -> move ctx keyA r to {ofAgreedTerms = t} `shouldReturn` refusedWith 400 "bad_request"
|
||||
post ctx (registrationPath (nrId r) <> "/name") "[]" `shouldReturn` refusedWith 400 "bad_request"
|
||||
stored ctx r `shouldReturn` r
|
||||
-- an owner in the body is ignored
|
||||
|
||||
@@ -36,30 +36,49 @@ namesSignatureTests = describe "StoreService names: signatures" $ do
|
||||
it "hashes ClearRecords" $
|
||||
clearRecordsDigest (resolverDomain chainId resolver) (namehash TLDSimplex "dynamis") 4 99 `shouldBe` hx "0xca4bd243288f27f38e3b45384ba36ce685ff155352cf8125f031e6fb189a46dd"
|
||||
describe "EIP-191 messages" $ do
|
||||
it "signs an order" $
|
||||
-- the contract's Appendix C, which the webapp computed with viem and pins in test/terms-vectors.test.ts
|
||||
it "signs an order with the terms version" $
|
||||
testMessage
|
||||
(orderMessage dynamisOrder timestamp)
|
||||
"simplex.domains order 0x78b602f03f164366531da963764c1f2fdfd588d0487daf2aa261ca799e5ffe33 7 69259500 0x9cf04ddef2eedfb02e6e7ba58f879c0636c584b6acc67ffe9f97a06bfb016ac1 0xb1971beb567c8dc1b2f01f0d823754c6168d8d6830db2221702a7678e1804674 1792574100"
|
||||
"0x5deee1b3589df9e53665e34aa65a491a4eabfd76a39207b0e9303457f7c44b7a"
|
||||
"0x73e32be461e99e64b093a4ad09b04b4d810e5623a1b0e9c9fdb4701ec2a6a2a84df9ffdccc39c5cb827cde4d846c7662f591665f1b868ab1c15778646d3f414e1b"
|
||||
(orderMessage termsOrder termsTimestamp)
|
||||
"simplex.domains order 0xbcc46b7948ff6be55bc3ff4db5bc4be16638f29e818a89244dae11caaed7c9c6 11 69292800 0x5e62a4cb7da596cb38169a6a552b5f69db2acee279b53be6693a78e03c3dfaa5 0x22709445157a2a1f7cb7e284e3ccf6c7ebc3b4d857002af452853c4a65907fe2 2026-10-23 1792828800"
|
||||
"0x50dfb031e558f78b83fa07c7ea0207c02a98d73751e43bc754dd26b7bc92b840"
|
||||
"0x17107b11494e25098efc348aca64a2469ffb832bcc79dc57377a1b26a4004b6701ad73648f50af8b39d4079820a3de7e681ddf15f8cf15b5cff02b4a48cf1fe61c"
|
||||
it "signs an order with the community terms" $
|
||||
testMessage
|
||||
(orderMessage communityOrder termsTimestamp)
|
||||
"simplex.domains order 0xbcc46b7948ff6be55bc3ff4db5bc4be16638f29e818a89244dae11caaed7c9c6 11 69292800 0x5e62a4cb7da596cb38169a6a552b5f69db2acee279b53be6693a78e03c3dfaa5 0x22709445157a2a1f7cb7e284e3ccf6c7ebc3b4d857002af452853c4a65907fe2 2026-10-23/community 1792828800"
|
||||
"0xf8521525761939c4cef8979cb54217e9e32b1a57999fc2f03e486f4eb1903802"
|
||||
"0x03eccb591b79ab4e3e6f22ccbeb77a6ac32c6dad8cb318557836258c390bc31d78852a3279c6c8cc2ef8d1025641d45ba4101f54dfd84c264deb2ab391c1cbc61c"
|
||||
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" $
|
||||
it "signs another name for an order with the terms version" $
|
||||
testMessage
|
||||
(retargetMessage registrationId dynamisOrder {ofLabelHash = labelHash "another"} timestamp)
|
||||
"simplex.domains name t2Jk8RmQ4vXw9NcP3yLb6g 0x5bfbbf75311ec002834031d73fa30bfe8bdf675f4a3df72443841490c4b49d02 7 69259500 0x9cf04ddef2eedfb02e6e7ba58f879c0636c584b6acc67ffe9f97a06bfb016ac1 0xb1971beb567c8dc1b2f01f0d823754c6168d8d6830db2221702a7678e1804674 1792574100"
|
||||
"0x8bc79aa98fbd53013e8961db27554ecd17139e14ec168b86afbaf2fc910bfc52"
|
||||
"0xe3d680bcc62255179d5260beeb920319c86bd919f6b01927bee01509cf6c1fe3121151947fb9e3a310e6652c0c9a9bf7ceb48e8d903774a38a3162d916e8b4591b"
|
||||
it "signs a spend from an order's balance, as the webapp signs it" $
|
||||
(retargetMessage registrationId termsOrder termsTimestamp)
|
||||
"simplex.domains name t2Jk8RmQ4vXw9NcP3yLb6g 0xbcc46b7948ff6be55bc3ff4db5bc4be16638f29e818a89244dae11caaed7c9c6 11 69292800 0x5e62a4cb7da596cb38169a6a552b5f69db2acee279b53be6693a78e03c3dfaa5 0x22709445157a2a1f7cb7e284e3ccf6c7ebc3b4d857002af452853c4a65907fe2 2026-10-23 1792828800"
|
||||
"0x01eda78f875428396a940b658b910b6eb6dc0bce82860c17d59f8f16e7767d25"
|
||||
"0x1d29abf2489da963d59d0982074b5af770efbaf96be3c756afbd6a7fea672b507e756944da0901724e2a5d7cfe2419ccb7573310ae65e65f4fd35d7c6fe494b51c"
|
||||
it "signs another name for an order with the community terms" $
|
||||
testMessage
|
||||
(spendMessage registrationId spendOrder timestamp)
|
||||
"simplex.domains spend t2Jk8RmQ4vXw9NcP3yLb6g 0x5bfbbf75311ec002834031d73fa30bfe8bdf675f4a3df72443841490c4b49d02 7 69259500 0xbb87d2d7aa14251a04cdd68b5d878d0b4c44c6c76486d90a534dab9525105e33 0x51431a705eb4903084cc8dbf3c1b966c9372914511c4623c889d49fdb8047da4 1792574100"
|
||||
"0x72c2f4b7d84839fb97eb78668355a02fc0b18e211f264fca97d19a8f36decde3"
|
||||
"0x8668cb4b72295479b4f9bcc2fd07fabfec2b4b8ae9230f2553536acb93c3e0cf6b2dad928a9a11b003c43ab43edeec117ce01c9214b8e99fe75fd547425e74481b"
|
||||
(retargetMessage registrationId communityOrder termsTimestamp)
|
||||
"simplex.domains name t2Jk8RmQ4vXw9NcP3yLb6g 0xbcc46b7948ff6be55bc3ff4db5bc4be16638f29e818a89244dae11caaed7c9c6 11 69292800 0x5e62a4cb7da596cb38169a6a552b5f69db2acee279b53be6693a78e03c3dfaa5 0x22709445157a2a1f7cb7e284e3ccf6c7ebc3b4d857002af452853c4a65907fe2 2026-10-23/community 1792828800"
|
||||
"0x4f1af02904f9f8863e92169f6e1a1d6733a3a25c5d22ffde7ac00f09a660c022"
|
||||
"0x30d027224b85cd9ac638edec1266748f21f46279eda72e68448f97b8fce73ca06c401035ad5416fc1e48f39646d0637fcaad1588ec74562ff842821b33e299361b"
|
||||
it "signs a spend from an order's balance with the terms version" $
|
||||
testMessage
|
||||
(spendMessage registrationId termsOrder termsTimestamp)
|
||||
"simplex.domains spend t2Jk8RmQ4vXw9NcP3yLb6g 0xbcc46b7948ff6be55bc3ff4db5bc4be16638f29e818a89244dae11caaed7c9c6 11 69292800 0x5e62a4cb7da596cb38169a6a552b5f69db2acee279b53be6693a78e03c3dfaa5 0x22709445157a2a1f7cb7e284e3ccf6c7ebc3b4d857002af452853c4a65907fe2 2026-10-23 1792828800"
|
||||
"0x109f0bd4f5eb1aa054f17c82a0b04dbd5d1e7b23c5ee91cf69464fb1435eb034"
|
||||
"0x20e930097b35350861332afa4b0b5dcff746aa879875269cc784575e6bb871711d3f6b1e11437d0a616a37a8b5b1ebbdd4c98aee04e09405ac3fa55a6af6ff831b"
|
||||
it "signs a spend from an order's balance with the community terms" $
|
||||
testMessage
|
||||
(spendMessage registrationId communityOrder termsTimestamp)
|
||||
"simplex.domains spend t2Jk8RmQ4vXw9NcP3yLb6g 0xbcc46b7948ff6be55bc3ff4db5bc4be16638f29e818a89244dae11caaed7c9c6 11 69292800 0x5e62a4cb7da596cb38169a6a552b5f69db2acee279b53be6693a78e03c3dfaa5 0x22709445157a2a1f7cb7e284e3ccf6c7ebc3b4d857002af452853c4a65907fe2 2026-10-23/community 1792828800"
|
||||
"0xd114398466a7a6d7d9a183feb4624e06c41d1dcdfb6b69e896ff9d74c70845c3"
|
||||
"0x90d2ab549b2cf5fd51442ac3f410b934e7fa45bd12853cfbfa2f8ab4876ed5600b9fe3a90459140f180949353e7dc3f56cd1447de31ae5c05e12f7bf6a113f061c"
|
||||
describe "signature format" $ do
|
||||
it "refuses a high s, though recovery accepts it" testHighS
|
||||
it "refuses v other than 27 or 28" $
|
||||
@@ -112,10 +131,11 @@ 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)
|
||||
recoverAddress RecoverableSignature {rsR, rsS, rsRecId = B.head v - 27} (personalDigest $ orderMessage termsOrder termsTimestamp)
|
||||
`shouldReturn` Right owner
|
||||
where
|
||||
highS = "0x73e32be461e99e64b093a4ad09b04b4d810e5623a1b0e9c9fdb4701ec2a6a2a8b206002333c63a347d8321b27b93899bc51d768793c21589fe7ae62862f6fff31c"
|
||||
-- the order vector's signature with s replaced by n - s and v flipped
|
||||
highS = "0x17107b11494e25098efc348aca64a2469ffb832bcc79dc57377a1b26a4004b67fe528c9b70af5074c62bf867df5c21805290fdd0b6798a85efe233428767215b1b"
|
||||
|
||||
-- fixtures
|
||||
|
||||
@@ -132,26 +152,20 @@ dynamis =
|
||||
rChannelLink = Nothing
|
||||
}
|
||||
|
||||
dynamisOrder :: OrderFields
|
||||
dynamisOrder =
|
||||
-- Appendix C's derived inputs: alicebakery with no links, its commitment as the webapp builds it, and a new seal under the fixture seed and a zero IV
|
||||
termsOrder :: OrderFields
|
||||
termsOrder =
|
||||
OrderFields
|
||||
{ ofLabelHash = labelHash "dynamis",
|
||||
ofLength = 7,
|
||||
ofDuration = duration,
|
||||
ofCommitment = commitment,
|
||||
ofSealed = "q3Zk0Yx7c2VhbGVkLW9yZGVyLWZpeHR1cmU"
|
||||
{ ofLabelHash = labelHash "alicebakery",
|
||||
ofLength = 11,
|
||||
ofDuration = 69292800,
|
||||
ofCommitment = hx "0x5e62a4cb7da596cb38169a6a552b5f69db2acee279b53be6693a78e03c3dfaa5",
|
||||
ofSealed = "AAAAAAAAAAAAAAAACWgxqMk-UKfpra_Zh6vc4KNBR6SX1WYpGb4COoWCCbM0bisYCH14WLzghp8hqi_-KyXSi4qf_DAdP_M4bFetD0s",
|
||||
ofAgreedTerms = "2026-10-23"
|
||||
}
|
||||
|
||||
-- the webapp's spend vector (test/flow.test.ts, SPEND): "another" with no links, sealed under a zero IV
|
||||
spendOrder :: OrderFields
|
||||
spendOrder =
|
||||
OrderFields
|
||||
{ ofLabelHash = labelHash "another",
|
||||
ofLength = 7,
|
||||
ofDuration = duration,
|
||||
ofCommitment = hx "0xbb87d2d7aa14251a04cdd68b5d878d0b4c44c6c76486d90a534dab9525105e33",
|
||||
ofSealed = "AAAAAAAAAAAAAAAACWgxqMk-UKfpra_bgbzR57AIDvSNmCR_G7IYbN7DX70sL2FVRHB1VfC0wdF-tc3ajHM3o8koM159Ja8hyg"
|
||||
}
|
||||
communityOrder :: OrderFields
|
||||
communityOrder = termsOrder {ofAgreedTerms = "2026-10-23/community"}
|
||||
|
||||
commitment :: ByteString
|
||||
commitment = hx "0x9cf04ddef2eedfb02e6e7ba58f879c0636c584b6acc67ffe9f97a06bfb016ac1"
|
||||
@@ -163,6 +177,10 @@ node = hx "0x833cc73a7eae1ddee67ec8845ec3d39799406c7bc87fab7c504a7e0ab5cb89bb"
|
||||
timestamp :: Word64
|
||||
timestamp = 1792574100
|
||||
|
||||
-- 24 October 2026 08:00 UTC
|
||||
termsTimestamp :: Word64
|
||||
termsTimestamp = 1792828800
|
||||
|
||||
-- 2 years of 365 days and the free days until 1 January 2027
|
||||
duration :: Word64
|
||||
duration = 69259500
|
||||
@@ -171,7 +189,7 @@ registrationId :: RegistrationId
|
||||
registrationId = RegistrationId "t2Jk8RmQ4vXw9NcP3yLb6g"
|
||||
|
||||
orderSignature :: Text
|
||||
orderSignature = "0x73e32be461e99e64b093a4ad09b04b4d810e5623a1b0e9c9fdb4701ec2a6a2a84df9ffdccc39c5cb827cde4d846c7662f591665f1b868ab1c15778646d3f414e1b"
|
||||
orderSignature = "0x17107b11494e25098efc348aca64a2469ffb832bcc79dc57377a1b26a4004b6701ad73648f50af8b39d4079820a3de7e681ddf15f8cf15b5cff02b4a48cf1fe61c"
|
||||
|
||||
retString :: ByteString
|
||||
retString = hx "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002168747470733a2f2f736d70382e73696d706c65782e696d2f6123636f6e7461637400000000000000000000000000000000000000000000000000000000000000"
|
||||
|
||||
@@ -9,7 +9,7 @@ import Bots.StoreService.BotTests (operatorCmd, withStoreService)
|
||||
import Bots.StoreService.FakeBTCPay (FakeBTCPay (..), setInvoiceState, setRates, webhookEvent, webhookSigHeader, withFakeBTCPay)
|
||||
import Bots.StoreService.FakeEthNode
|
||||
import Bots.StoreService.NamesContractsTests (reservedNamesCall)
|
||||
import Bots.StoreService.NamesOrdersTests (Ctx (..), Order (..), advance, aliceOrder, commitmentOf, field, fieldsOf, invoiceRegistration, keyA, keyB, orderTime, ownerAText, placeOrder, placed, post, refusedWith, registrationById, registrationOf, registrationsOf, settleNow, withOrdersOn, yearsDuration)
|
||||
import Bots.StoreService.NamesOrdersTests (Ctx (..), Order (..), advance, aliceOrder, commitmentOf, field, fieldsOf, invoiceRegistration, keyA, keyB, malformedTerms, orderTime, ownerAText, placeOrder, placed, post, refusedWith, registrationById, registrationOf, registrationsOf, settleNow, withOrdersOn, yearsDuration)
|
||||
import Bots.StoreService.NamesOwnerTests (findBody, postFind)
|
||||
import Bots.StoreService.NamesStoreTests (at, order, ownerA, placePaid, reg, t0, write)
|
||||
import Bots.StoreService.WebTests (StubCall (..), capturingStderr, StubState (..), jsonObject, rcv, sampleProviderInvoice, statusOf, stubCalls, webGet, webRequestBody, withServiceStores)
|
||||
@@ -17,7 +17,7 @@ import ChatTests.DBUtils (TestParams)
|
||||
import Control.Concurrent.Async (Async, async, cancel, concurrently, wait)
|
||||
import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar)
|
||||
import Control.Concurrent.STM (atomically, check)
|
||||
import Control.Monad (join, replicateM_, void)
|
||||
import Control.Monad (forM_, join, replicateM_, void)
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.Aeson.KeyMap as KM
|
||||
import qualified Data.Aeson.Types as JT
|
||||
@@ -182,12 +182,13 @@ spendBody Ctx {cRandom} source SpendBody {sbOrder = o@Order {oKey, oPrice, oTime
|
||||
("sealed", J.String ofSealed),
|
||||
("price", J.toJSON oPrice),
|
||||
("balance", J.toJSON sbBalance),
|
||||
("terms", J.String ofAgreedTerms),
|
||||
("timestamp", J.toJSON oTimestamp),
|
||||
("signature", J.String $ hex $ serializeSignature sig)
|
||||
]
|
||||
<> maybe [] (\m -> [("method", J.String m)]) sbMethod
|
||||
where
|
||||
fields@OrderFields {ofLabelHash = LabelHash lh, ofLength, ofDuration, ofCommitment, ofSealed} = fieldsOf o
|
||||
fields@OrderFields {ofLabelHash = LabelHash lh, ofLength, ofDuration, ofCommitment, ofSealed, ofAgreedTerms} = fieldsOf o
|
||||
|
||||
postSpend :: Ctx -> RegistrationId -> J.Object -> IO (Int, Maybe J.Object)
|
||||
postSpend ctx (RegistrationId rid) = post ctx ("/api/registration/" <> T.unpack rid <> "/spend") . J.encode
|
||||
@@ -259,6 +260,7 @@ testCoveredSpend = withSpend $ \ctx -> do
|
||||
r <- registrationById ctx rid
|
||||
(nrPhase r, nrLive r, nrPrice r, nrPayment r, nrBalanceSource r, nrBalanceUsed r) `shouldBe` (PQueued, True, 10000, RPBalance, Just src, 10000)
|
||||
nrOrderHash r `shouldBe` orderHash ownerA (fieldsOf aliceOrder) (OTBalance src 10000 15000 Nothing)
|
||||
nrAgreedTerms r `shouldBe` oTerms aliceOrder
|
||||
view <- viewOf ctx rid
|
||||
(field "phase" view, field "price" view, field "fromBalance" view, field "balance" view) `shouldBe` (Just "queued", Just (J.Number 10000), Just (J.Number 10000), Just J.Null)
|
||||
amountOf ctx src `shouldReturn` Just 5000
|
||||
@@ -282,6 +284,7 @@ testDifferenceSpend = withSpend $ \ctx -> do
|
||||
holder <- invoiceRegistration ctx invId
|
||||
(nrPhase holder, nrLive holder, nrPrice holder, nrPayment holder, nrBalanceSource holder, nrBalanceUsed holder) `shouldBe` (PUnpaid, True, 10000, RPInvoice btc invId, Just src, 6000)
|
||||
nrOrderHash holder `shouldBe` orderHash ownerA (fieldsOf aliceOrder) (OTBalance src 10000 6000 (Just btc))
|
||||
nrAgreedTerms holder `shouldBe` oTerms aliceOrder
|
||||
fmap irAmount <$> getInvoice (cStore ctx) invId `shouldReturn` Just (CurrencyAmount 4000)
|
||||
field "balance" <$> viewOf ctx src `shouldReturn` Just (J.object ["amount" J..= (6000 :: Int), "held" J..= True, "heldUntil" J..= expiry, "refunded" J..= (0 :: Int)])
|
||||
|
||||
@@ -432,6 +435,9 @@ testSpendSizes = withSpend $ \ctx -> do
|
||||
src <- sourceOf ctx "src" 15000
|
||||
missing <- KM.delete "sealed" <$> spendBody ctx src (covered 15000)
|
||||
postSpend ctx src missing `shouldReturn` refusedWith 400 "bad_request"
|
||||
noTerms <- KM.delete "terms" <$> spendBody ctx src (covered 15000)
|
||||
postSpend ctx src noTerms `shouldReturn` refusedWith 400 "bad_request"
|
||||
forM_ malformedTerms $ \t -> spend ctx src (covered 15000) {sbOrder = aliceOrder {oTerms = t}} `shouldReturn` refusedWith 400 "bad_request"
|
||||
spend ctx src (covered 15000) {sbOrder = aliceOrder {oSealed = T.replicate 11096 "a"}} `shouldReturn` refusedWith 400 "bad_request"
|
||||
padded <- KM.insert "padding" (J.String $ T.replicate 16384 "a") <$> spendBody ctx src (covered 15000)
|
||||
postSpend ctx src padded `shouldReturn` refusedWith 400 "bad_request"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
module Bots.StoreService.NamesStoreTests (namesStoreTests, at, btc, insertInvoiceRow, invoiceStatus, issue, newReg, order, ownerA, ownerB, paidOrder, placeCode, placePaid, price, reg, reveal, signature, t0, usable, write) where
|
||||
|
||||
import Bots.StoreService.ConfigTests (termsVersion)
|
||||
import Bots.StoreService.WebTests (columnsOf, digestFixture, runMigrations, withServiceStore)
|
||||
import Control.Arrow ((&&&))
|
||||
import Control.Monad (forM_, void)
|
||||
@@ -118,7 +119,8 @@ order label n =
|
||||
ofLength = T.length label,
|
||||
ofDuration = 69292800,
|
||||
ofCommitment = digestFixture n,
|
||||
ofSealed = "sealed-" <> label <> "-" <> T.pack (show n)
|
||||
ofSealed = "sealed-" <> label <> "-" <> T.pack (show n),
|
||||
ofAgreedTerms = termsVersion
|
||||
}
|
||||
|
||||
newReg :: Text -> Address -> OrderFields -> NewRegistration
|
||||
@@ -319,6 +321,7 @@ testOrderHashFields = do
|
||||
("owner", orderHash ownerB o paid),
|
||||
("commitment", orderHash ownerA o {ofCommitment = digestFixture 2} paid),
|
||||
("sealed", orderHash ownerA o {ofSealed = ofSealed o <> "x"} paid),
|
||||
("terms", orderHash ownerA o {ofAgreedTerms = ofAgreedTerms o <> "/community"} paid),
|
||||
("method", orderHash ownerA o (OTInvoice (SPMCard CPStripe) price)),
|
||||
("price", orderHash ownerA o (OTInvoice btc (price + 1))),
|
||||
("code instead of invoice", orderHash ownerA o (OTCode $ digestFixture 3)),
|
||||
@@ -332,6 +335,7 @@ testOrderHashBoundaries :: IO ()
|
||||
testOrderHashBoundaries = do
|
||||
let o = order "alicebakery" 1
|
||||
orderHash ownerA o {ofSealed = "ab"} (OTCode "c") `shouldNotBe` orderHash ownerA o {ofSealed = "a"} (OTCode "bc")
|
||||
orderHash ownerA o {ofSealed = "ab", ofAgreedTerms = "c"} (OTCode "d") `shouldNotBe` orderHash ownerA o {ofSealed = "a", ofAgreedTerms = "bc"} (OTCode "d")
|
||||
|
||||
testCreatePaid :: IO ()
|
||||
testCreatePaid = withServiceStore $ \st -> do
|
||||
@@ -348,6 +352,7 @@ testCreatePaid = withServiceStore $ \st -> do
|
||||
nrOwner = ownerA,
|
||||
nrCommitment = ofCommitment o,
|
||||
nrSealed = ofSealed o,
|
||||
nrAgreedTerms = termsVersion,
|
||||
nrPhase = PUnpaid,
|
||||
nrYears = twoYears,
|
||||
nrPrice = price,
|
||||
@@ -512,7 +517,8 @@ testRoundTrip = withServiceStore $ \st -> do
|
||||
nrFailedAt = Just (at 2),
|
||||
nrError = Just "The name was not signed in time",
|
||||
nrLinksRefusedAt = Just (at 4),
|
||||
nrMoves = 2
|
||||
nrMoves = 2,
|
||||
nrAgreedTerms = termsVersion <> "/community"
|
||||
}
|
||||
write st changed `shouldReturn` Written 2
|
||||
reg st "r1" `shouldReturn` changed {nrVersion = 2, nrLive = False, nrUpdatedAt = at 1}
|
||||
@@ -598,7 +604,7 @@ testWriteMeetsLiveOrder = withServiceStore $ \st -> do
|
||||
testRetarget :: IO ()
|
||||
testRetarget = withServiceStore $ \st -> do
|
||||
let o = order "alicebakery" 1
|
||||
o' = order "alicebakers" 2
|
||||
o' = (order "alicebakers" 2) {ofAgreedTerms = termsVersion <> "/community"}
|
||||
placePaid st "r1" ownerA o (at 60) `shouldReturn` Right ()
|
||||
r <- reg st "r1"
|
||||
let failed =
|
||||
@@ -624,6 +630,7 @@ testRetarget = withServiceStore $ \st -> do
|
||||
nrLabelHash = ofLabelHash o',
|
||||
nrCommitment = ofCommitment o',
|
||||
nrSealed = ofSealed o',
|
||||
nrAgreedTerms = ofAgreedTerms o',
|
||||
nrPrice = 10000,
|
||||
nrPhase = PQueued,
|
||||
nrMoves = 1,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
module Bots.StoreService.NamesWorkerTests (namesWorkerTests) where
|
||||
|
||||
import Bots.StoreService.ConfigTests (termsVersion)
|
||||
import Bots.StoreService.FakeEthNode
|
||||
import Bots.StoreService.NamesContractsTests (availableCall, maxCommitmentAgeCall, minCommitmentAgeCall, noncesCall, registrarOf, unhex)
|
||||
import Bots.StoreService.NamesOrdersTests
|
||||
@@ -505,7 +506,7 @@ testTwoWallets = withWorker $ \w@W {wCtx = ctx} -> do
|
||||
|
||||
-- | The order of alicebakery, by owner A, with this commitment.
|
||||
aliceFields :: Int -> OrderFields
|
||||
aliceFields n = OrderFields {ofLabelHash = labelHash "alicebakery", ofLength = 11, ofDuration = yearsDuration 2 orderTime, ofCommitment = commitmentOf n, ofSealed = "sealed-alicebakery-" <> tshow n}
|
||||
aliceFields n = OrderFields {ofLabelHash = labelHash "alicebakery", ofLength = 11, ofDuration = yearsDuration 2 orderTime, ofCommitment = commitmentOf n, ofSealed = "sealed-alicebakery-" <> tshow n, ofAgreedTerms = termsVersion}
|
||||
|
||||
aliceRegistration :: Text -> Int -> NewRegistration
|
||||
aliceRegistration rid n = NewRegistration {nrgId = RegistrationId rid, nrgOwner = ownerA, nrgOrder = aliceFields n, nrgYears = 2}
|
||||
|
||||
Reference in new issue
Block a user