mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-10-12 02:22:45 +00:00
[names] eth transaction signing for name registration (#1887)
* implement eth transaction signing * Update src/Simplex/Messaging/Eth/RLP.hs Co-authored-by: Evgeny <[email protected]> * fix Co-authored-by: Evgeny <[email protected]> * address review comments * self-review * $$, puns --------- Co-authored-by: Evgeny Poberezkin <[email protected]> Co-authored-by: Evgeny @ SimpleX Chat <[email protected]>
This commit is contained in:
co-authored by
Evgeny
Evgeny @ SimpleX Chat
parent
9f907a2740
commit
6770e732b7
@@ -10,12 +10,13 @@ held by the device.
|
||||
|
||||
## What is deliberately absent
|
||||
|
||||
- **No RLP encoder, and no transaction building.** RLP is only needed to
|
||||
construct raw transactions or EIP-7702 authorizations. The client does
|
||||
neither, so the `RSLV` resolver path in this repo stays strictly read-only.
|
||||
- **No signing and no stealth addresses.** This change adds key and address
|
||||
derivation only. Signing, the EIP-712 typed-data hashing it requires, and
|
||||
stealth addresses are added with the first code that uses them.
|
||||
- **No EIP-7702 authorizations.** RLP is implemented only as far as EIP-1559
|
||||
transactions need it, for registering names; the `RSLV` resolver path in
|
||||
this repo stays strictly read-only.
|
||||
- **No stealth addresses and no EIP-712.** Signing covers recoverable ECDSA
|
||||
over a 32-byte digest and EIP-1559 transactions, which name registration
|
||||
requires. EIP-712 typed-data hashing and stealth addresses are added with
|
||||
the first code that uses them.
|
||||
- **No BIP-32 public derivation.** We always hold the seed, so CKDpub, xpub
|
||||
serialization and fingerprints are not implemented. Non-hardened *private*
|
||||
derivation is, because BIP-44 paths end in non-hardened components.
|
||||
@@ -37,6 +38,8 @@ Simplex.Messaging.Crypto.BIP39.English embedded upstream 2048-word list
|
||||
Simplex.Messaging.Crypto.BIP32 HD derivation, WalletMaster
|
||||
Simplex.Messaging.Crypto.BIP44 BIP-44 paths, AccountIndex
|
||||
Simplex.Messaging.Eth.Address addresses, EIP-55
|
||||
Simplex.Messaging.Eth.RLP Recursive Length Prefix encoding
|
||||
Simplex.Messaging.Eth.Transaction signed EIP-1559 transactions
|
||||
```
|
||||
|
||||
`keccak256` is added to `Simplex.Messaging.Crypto`, next to `sha3_256`.
|
||||
@@ -211,12 +214,14 @@ c-sources: cbits/libsecp256k1/src/{secp256k1,precomputed_ecmult,precomputed_e
|
||||
No `include-dirs` are needed: every libsecp256k1 include is quoted and relative
|
||||
to the including file. The embedded wordlist makes `file-embed` a library
|
||||
dependency; before this change only the `smp-server` and `xftp-server`
|
||||
executables and the test suite used it.
|
||||
executables and the test suite used it. `wide-word` provides `Word128` and
|
||||
`Word256` for transaction fields, each as narrow as both geth and reth accept.
|
||||
|
||||
Built with no `-D` of our own. The table-size settings (`ECMULT_WINDOW_SIZE`,
|
||||
`COMB_BLOCKS`, `COMB_TEETH`) have `#ifndef` defaults in the headers, and the
|
||||
checked-in precomputed tables are generated for those defaults; every other
|
||||
option, such as the optional modules and x86-64 assembly, is off unless a `-D`
|
||||
Built with one `-D` of our own, `ENABLE_MODULE_RECOVERY=1`, for recoverable
|
||||
signatures. The table-size settings (`ECMULT_WINDOW_SIZE`, `COMB_BLOCKS`,
|
||||
`COMB_TEETH`) have `#ifndef` defaults in the headers, and the checked-in
|
||||
precomputed tables are generated for those defaults; every other option, such
|
||||
as the other optional modules and x86-64 assembly, is off unless a `-D`
|
||||
enables it. `secp256k1.c` defines `SECP256K1_BUILD` itself.
|
||||
|
||||
32-bit targets (armv7a-android, i686 musl) are covered by libsecp256k1's own
|
||||
@@ -269,7 +274,7 @@ the C code independently of the Haskell build.
|
||||
|
||||
## Tests
|
||||
|
||||
`tests/CoreTests/EthCryptoTests.hs`, 93 examples, with published vectors read
|
||||
`tests/CoreTests/EthCryptoTests.hs`, 104 examples, with published vectors read
|
||||
from vendored upstream files in `tests/fixtures`, each pinned by a sha256 test:
|
||||
|
||||
- **BIP-39**: all 24 English vectors of `trezor/python-mnemonic/vectors.json`:
|
||||
|
||||
+5
-2
@@ -155,6 +155,8 @@ library
|
||||
Simplex.Messaging.Encoding
|
||||
Simplex.Messaging.Encoding.String
|
||||
Simplex.Messaging.Eth.Address
|
||||
Simplex.Messaging.Eth.RLP
|
||||
Simplex.Messaging.Eth.Transaction
|
||||
Simplex.Messaging.Names.Record
|
||||
Simplex.Messaging.Notifications.Client
|
||||
Simplex.Messaging.Notifications.Protocol
|
||||
@@ -339,8 +341,8 @@ library
|
||||
cbits/blst/src
|
||||
cbits/libbbs/include
|
||||
cbits/libbbs/src
|
||||
-- libsecp256k1 requires no -D flags: every setting has an #ifndef default that matches the checked-in tables
|
||||
cc-options: -D__BLST_PORTABLE__
|
||||
-- libsecp256k1's only -D flag enables its recovery module: every other setting has an #ifndef default that matches the checked-in tables
|
||||
cc-options: -D__BLST_PORTABLE__ -DENABLE_MODULE_RECOVERY=1
|
||||
if flag(commoncrypto)
|
||||
cc-options: -DBBS_CRYPTO_CC
|
||||
frameworks: Security
|
||||
@@ -408,6 +410,7 @@ library
|
||||
, transformers ==0.6.*
|
||||
, unliftio ==0.2.*
|
||||
, unliftio-core ==0.2.*
|
||||
, wide-word ==0.1.6.*
|
||||
, yaml ==0.11.*
|
||||
, zstd ==0.1.3.*
|
||||
default-language: Haskell2010
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{-# LANGUAGE ForeignFunctionInterface #-}
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
|
||||
-- | FFI bindings to libsecp256k1.
|
||||
@@ -7,6 +8,7 @@ module Simplex.Messaging.Crypto.Secp256k1
|
||||
Secp256k1PublicKey,
|
||||
Secp256k1Context,
|
||||
PubKeyFormat (..),
|
||||
RecoverableSignature (..),
|
||||
mkPrivateKey,
|
||||
unPrivateKey,
|
||||
withContext,
|
||||
@@ -14,6 +16,7 @@ module Simplex.Messaging.Crypto.Secp256k1
|
||||
secp256k1PublicKey_,
|
||||
serializePublicKey,
|
||||
privateKeyTweakAdd,
|
||||
signRecoverable,
|
||||
)
|
||||
where
|
||||
|
||||
@@ -26,6 +29,7 @@ import Crypto.Random (ChaChaDRG)
|
||||
import Data.ByteArray (ScrubbedBytes)
|
||||
import qualified Data.ByteArray as BA
|
||||
import Data.ByteString (ByteString)
|
||||
import qualified Data.ByteString as B
|
||||
import Foreign hiding (void)
|
||||
import Foreign.C
|
||||
import qualified Simplex.Messaging.Crypto as C
|
||||
@@ -46,6 +50,16 @@ uncompressedSize = 65
|
||||
pubKeyInternalSize :: Int
|
||||
pubKeyInternalSize = 64
|
||||
|
||||
-- | Internal size of @secp256k1_ecdsa_recoverable_signature@.
|
||||
recSigInternalSize :: Int
|
||||
recSigInternalSize = 65
|
||||
|
||||
compactSigSize :: Int
|
||||
compactSigSize = 64
|
||||
|
||||
digestSize :: Int
|
||||
digestSize = 32
|
||||
|
||||
-- Types
|
||||
|
||||
newtype Secp256k1PrivateKey = Secp256k1PrivateKey ScrubbedBytes
|
||||
@@ -59,12 +73,22 @@ newtype Secp256k1Context = Secp256k1Context (Ptr Ctx)
|
||||
data PubKeyFormat = Compressed | Uncompressed
|
||||
deriving (Eq, Show)
|
||||
|
||||
-- | @r@ and @s@, 32 bytes big-endian each, and the recovery id, EIP-1559's @yParity@.
|
||||
data RecoverableSignature = RecoverableSignature
|
||||
{ rsR :: ByteString,
|
||||
rsS :: ByteString,
|
||||
rsRecId :: Word8
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
-- FFI
|
||||
|
||||
data Ctx
|
||||
|
||||
data PubKeyRaw
|
||||
|
||||
data RecSigRaw
|
||||
|
||||
foreign import ccall "secp256k1_context_create"
|
||||
c_context_create :: CUInt -> IO (Ptr Ctx)
|
||||
|
||||
@@ -86,6 +110,12 @@ foreign import ccall "secp256k1_ec_pubkey_serialize"
|
||||
foreign import ccall "secp256k1_ec_seckey_tweak_add"
|
||||
c_ec_seckey_tweak_add :: Ptr Ctx -> Ptr Word8 -> Ptr Word8 -> IO CInt
|
||||
|
||||
foreign import ccall "secp256k1_ecdsa_sign_recoverable"
|
||||
c_ecdsa_sign_recoverable :: Ptr Ctx -> Ptr RecSigRaw -> Ptr Word8 -> Ptr Word8 -> Ptr () -> Ptr () -> IO CInt
|
||||
|
||||
foreign import ccall "secp256k1_ecdsa_recoverable_signature_serialize_compact"
|
||||
c_ecdsa_recoverable_signature_serialize_compact :: Ptr Ctx -> Ptr Word8 -> Ptr CInt -> Ptr RecSigRaw -> IO CInt
|
||||
|
||||
-- SECP256K1_CONTEXT_NONE = SECP256K1_FLAGS_TYPE_CONTEXT
|
||||
contextNone :: CUInt
|
||||
contextNone = 1
|
||||
@@ -146,3 +176,20 @@ privateKeyTweakAdd (Secp256k1PrivateKey sk) tweak
|
||||
BA.withByteArray tweak $ \twPtr -> do
|
||||
(rc, sk') <- BA.copyRet sk $ \skPtr -> c_ec_seckey_tweak_add staticContext skPtr twPtr
|
||||
pure $ if rc == 1 then Just (Secp256k1PrivateKey sk') else Nothing
|
||||
|
||||
-- | Signs a 32-byte digest, deterministically (RFC 6979) and always low-@s@, as EIP-2 requires.
|
||||
signRecoverable :: TVar ChaChaDRG -> Secp256k1PrivateKey -> ByteString -> IO (Either String RecoverableSignature)
|
||||
signRecoverable g (Secp256k1PrivateKey sk) digest
|
||||
| B.length digest /= digestSize = pure $ Left $ "digest: expected 32 bytes, got " <> show (B.length digest)
|
||||
| otherwise = withContext g $ \(Secp256k1Context ctx) ->
|
||||
allocaBytes recSigInternalSize $ \sigPtr -> do
|
||||
rc <- BA.withByteArray digest $ \msgPtr -> BA.withByteArray sk $ \skPtr -> c_ecdsa_sign_recoverable ctx sigPtr msgPtr skPtr nullPtr nullPtr
|
||||
if rc /= 1
|
||||
then pure $ Left "secp256k1_ecdsa_sign_recoverable failed on a validated key"
|
||||
else do
|
||||
(recId, compact) <- BA.allocRet compactSigSize $ \outPtr ->
|
||||
alloca $ \recIdPtr -> do
|
||||
void $ c_ecdsa_recoverable_signature_serialize_compact ctx outPtr recIdPtr sigPtr
|
||||
peek recIdPtr
|
||||
let (rsR, rsS) = B.splitAt (compactSigSize `div` 2) compact
|
||||
pure $ Right RecoverableSignature {rsR, rsS, rsRecId = fromIntegral recId}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
module Simplex.Messaging.Eth.Address
|
||||
( Address,
|
||||
unAddress,
|
||||
addressFromPublicKey,
|
||||
addressFromPrivateKey,
|
||||
deriveAddress,
|
||||
@@ -48,6 +49,9 @@ instance StrEncoding Address where
|
||||
addressSize :: Int
|
||||
addressSize = 20
|
||||
|
||||
unAddress :: Address -> ByteString
|
||||
unAddress (Address bs) = bs
|
||||
|
||||
-- | The last 20 bytes of @keccak256@ of the uncompressed public key with its @0x04@ SEC1 prefix removed.
|
||||
addressFromPublicKey :: S.Secp256k1PublicKey -> IO Address
|
||||
addressFromPublicKey pk = Address . B.drop 12 . keccak256 . B.drop 1 <$> S.serializePublicKey S.Uncompressed pk
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
|
||||
-- | Recursive Length Prefix, the encoding of Ethereum transactions.
|
||||
module Simplex.Messaging.Eth.RLP
|
||||
( RLPItem (..),
|
||||
rlpEncode,
|
||||
scalarItem,
|
||||
)
|
||||
where
|
||||
|
||||
import Data.ByteString (ByteString)
|
||||
import qualified Data.ByteString as B
|
||||
import Data.Word (Word32)
|
||||
import Simplex.Messaging.Encoding (smpEncode)
|
||||
|
||||
data RLPItem = RLPBytes ByteString | RLPList [RLPItem]
|
||||
|
||||
rlpEncode :: RLPItem -> ByteString
|
||||
rlpEncode = \case
|
||||
RLPBytes b
|
||||
| B.length b == 1 && B.head b < 0x80 -> b
|
||||
| otherwise -> lengthPrefixed 0x80 b
|
||||
RLPList items -> lengthPrefixed 0xc0 $ B.concat $ map rlpEncode items
|
||||
where
|
||||
lengthPrefixed offset payload
|
||||
| len < 56 = B.cons (offset + fromIntegral len) payload
|
||||
| otherwise = B.cons (offset + 55 + fromIntegral (B.length lenBytes)) lenBytes <> payload
|
||||
where
|
||||
len = B.length payload
|
||||
lenBytes = B.dropWhile (== 0) $ smpEncode (fromIntegral len :: Word32)
|
||||
|
||||
-- | Big-endian with no leading zeros, and zero as the empty string.
|
||||
scalarItem :: ByteString -> RLPItem
|
||||
scalarItem = RLPBytes . B.dropWhile (== 0)
|
||||
@@ -0,0 +1,58 @@
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
|
||||
-- | EIP-1559 (type 2) transactions with an empty access list, signed for @eth_sendRawTransaction@.
|
||||
module Simplex.Messaging.Eth.Transaction
|
||||
( Eip1559Tx (..),
|
||||
signEip1559Tx,
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Concurrent.STM (TVar)
|
||||
import Crypto.Random (ChaChaDRG)
|
||||
import Data.Bits (shiftR)
|
||||
import Data.ByteString (ByteString)
|
||||
import qualified Data.ByteString as B
|
||||
import Data.WideWord.Word128 (Word128 (..))
|
||||
import Data.WideWord.Word256 (Word256 (..))
|
||||
import Data.Word (Word32, Word64)
|
||||
import Simplex.Messaging.Crypto (keccak256)
|
||||
import Simplex.Messaging.Crypto.Secp256k1 (RecoverableSignature (..), Secp256k1PrivateKey, signRecoverable)
|
||||
import Simplex.Messaging.Encoding (smpEncode)
|
||||
import Simplex.Messaging.Eth.Address (Address, unAddress)
|
||||
import Simplex.Messaging.Eth.RLP
|
||||
import Simplex.Messaging.Util ((<$$>))
|
||||
|
||||
-- | Each width is the narrowest that both geth and reth accept.
|
||||
data Eip1559Tx = Eip1559Tx
|
||||
{ txChainId :: Word64,
|
||||
txNonce :: Word64,
|
||||
txMaxPriorityFeePerGas :: Word128,
|
||||
txMaxFeePerGas :: Word128,
|
||||
txGasLimit :: Word64,
|
||||
txTo :: Address,
|
||||
txValue :: Word256,
|
||||
txData :: ByteString
|
||||
}
|
||||
|
||||
-- | @0x02 || rlp([chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, accessList, yParity, r, s])@, signing @keccak256@ of the same without the last three.
|
||||
signEip1559Tx :: TVar ChaChaDRG -> Secp256k1PrivateKey -> Eip1559Tx -> IO (Either String ByteString)
|
||||
signEip1559Tx g sk Eip1559Tx {txChainId, txNonce, txMaxPriorityFeePerGas, txMaxFeePerGas, txGasLimit, txTo, txValue, txData} =
|
||||
signed <$$> signRecoverable g sk (keccak256 $ typed fields)
|
||||
where
|
||||
signed RecoverableSignature {rsR, rsS, rsRecId} = typed $ fields <> [scalarItem (B.singleton rsRecId), scalarItem rsR, scalarItem rsS]
|
||||
fields =
|
||||
[ w64 txChainId,
|
||||
w64 txNonce,
|
||||
w128 txMaxPriorityFeePerGas,
|
||||
w128 txMaxFeePerGas,
|
||||
w64 txGasLimit,
|
||||
RLPBytes $ unAddress txTo,
|
||||
w256 txValue,
|
||||
RLPBytes txData,
|
||||
RLPList []
|
||||
]
|
||||
w64 = scalarItem . be64
|
||||
w128 (Word128 hi lo) = scalarItem $ be64 hi <> be64 lo
|
||||
w256 (Word256 a3 a2 a1 a0) = scalarItem $ be64 a3 <> be64 a2 <> be64 a1 <> be64 a0
|
||||
be64 n = smpEncode (fromIntegral (n `shiftR` 32) :: Word32) <> smpEncode (fromIntegral n :: Word32)
|
||||
typed items = B.cons 0x02 $ rlpEncode $ RLPList items
|
||||
@@ -32,6 +32,9 @@ import Simplex.Messaging.Crypto.BIP44
|
||||
import qualified Simplex.Messaging.Crypto.Secp256k1 as S
|
||||
import Simplex.Messaging.Encoding.String (strDecode, strEncode)
|
||||
import Simplex.Messaging.Eth.Address
|
||||
import Simplex.Messaging.Eth.RLP
|
||||
import Simplex.Messaging.Eth.Transaction
|
||||
import Simplex.Messaging.Util ((<$$>))
|
||||
import Test.Hspec hiding (fit, it)
|
||||
import Util
|
||||
|
||||
@@ -44,6 +47,9 @@ ethCryptoTests = do
|
||||
describe "BIP-32" $ bip32Tests g
|
||||
describe "BIP-44 derivation" $ derivationTests g
|
||||
describe "EIP-55 addresses" eip55Tests
|
||||
describe "RLP" rlpTests
|
||||
describe "recoverable signing" $ signingTests g
|
||||
describe "EIP-1559 transactions" $ transactionTests g
|
||||
|
||||
-- helpers
|
||||
|
||||
@@ -307,3 +313,58 @@ eip55Tests = do
|
||||
"0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB",
|
||||
"0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb"
|
||||
]
|
||||
|
||||
rlpTests :: Spec
|
||||
rlpTests = do
|
||||
forM_ rlpVectors $ \(name, item, expected) ->
|
||||
it ("encodes " <> name) $ toHex (rlpEncode item) `shouldBe` expected
|
||||
it "encodes scalars without leading zeros" $ do
|
||||
toHex (rlpEncode $ scalarItem $ B.replicate 32 0) `shouldBe` "80"
|
||||
toHex (rlpEncode $ scalarItem $ hx "000f") `shouldBe` "0f"
|
||||
toHex (rlpEncode $ scalarItem $ hx "000400") `shouldBe` "820400"
|
||||
where
|
||||
rlpVectors =
|
||||
[ ("the empty string", RLPBytes "", "80"),
|
||||
("the empty list", RLPList [], "c0"),
|
||||
("dog", RLPBytes "dog", "83646f67"),
|
||||
("[cat, dog]", RLPList [RLPBytes "cat", RLPBytes "dog"], "c88363617483646f67"),
|
||||
("the set-theoretic three", RLPList [RLPList [], RLPList [RLPList []], RLPList [RLPList [], RLPList [RLPList []]]], "c7c0c1c0c3c0c1c0"),
|
||||
("a 56-byte string", RLPBytes (B.replicate 56 0x61), "b838" <> B.concat (replicate 56 "61"))
|
||||
]
|
||||
|
||||
-- expected signatures and transactions are viem's for the same key and inputs
|
||||
signingTests :: TVar ChaChaDRG -> Spec
|
||||
signingTests g = do
|
||||
it "derives the EIP-155 example address" $
|
||||
(strEncode <$> addressFromPrivateKey g txKey) `shouldReturn` "0x9d8A62f656a8d1615C1294fd71e9CFb3E4855A4F"
|
||||
it "signs a digest as viem does" $
|
||||
S.signRecoverable g txKey (C.keccak256 "simplex")
|
||||
`shouldReturn` Right
|
||||
S.RecoverableSignature
|
||||
{ S.rsR = hx "8d5aa94d4288fb8297dd9ff2c9cc5026b09b5305ace1f0b48a864ab116f2d3b0",
|
||||
S.rsS = hx "3eb3b2c117d918736df192c69cef499294f2a070aff74c2bf69776a179b7cdb1",
|
||||
S.rsRecId = 1
|
||||
}
|
||||
it "refuses a digest that is not 32 bytes" $
|
||||
(isLeft <$> S.signRecoverable g txKey "digest") `shouldReturn` True
|
||||
|
||||
transactionTests :: TVar ChaChaDRG -> Spec
|
||||
transactionTests g = do
|
||||
it "signs a contract call as viem does" $
|
||||
(toHex <$$> signEip1559Tx g txKey contractCall)
|
||||
`shouldReturn` Right "02f89383aa36a709843b9aca008506fc23ac00830186a094353535353535353535353535353535353535353580a4f14fcbc81111111111111111111111111111111111111111111111111111111111111111c001a08be78ee2e58a62763be161e3c4440c74c3b6ae56d667b143554e12239a6328e5a0790d86c0398a5295d981d12f4abc10d61b58917b7b7fd21215f7686457aafc19"
|
||||
it "signs a value transfer with zero fields as viem does" $
|
||||
(toHex <$$> signEip1559Tx g txKey transfer)
|
||||
`shouldReturn` Right "02f86a01808001825208943535353535353535353535353535353535353535880de0b6b3a764000080c001a0d04dbcdcfa4c64fe9deb8f0fea4d7a1f07407e80fd2b066b8f6d58a9baf6af0ba074c14688697464225bbe29301db1df7f0c1b9484a52eaf76162524baba2f2791"
|
||||
it "encodes every word of fields wider than 64 bits" $
|
||||
(B.isInfixOf wideFields . toHex <$$> signEip1559Tx g txKey wide)
|
||||
`shouldReturn` Right True
|
||||
where
|
||||
to = right $ strDecode "0x3535353535353535353535353535353535353535"
|
||||
contractCall = Eip1559Tx {txChainId = 11155111, txNonce = 9, txMaxPriorityFeePerGas = 1000000000, txMaxFeePerGas = 30000000000, txGasLimit = 100000, txTo = to, txValue = 0, txData = hx "f14fcbc8" <> B.replicate 32 0x11}
|
||||
transfer = Eip1559Tx {txChainId = 1, txNonce = 0, txMaxPriorityFeePerGas = 0, txMaxFeePerGas = 1, txGasLimit = 21000, txTo = to, txValue = 1000000000000000000, txData = ""}
|
||||
wide = Eip1559Tx {txChainId = maxBound, txNonce = maxBound - 1, txMaxPriorityFeePerGas = 0x10000000000000000000000007, txMaxFeePerGas = 0x80000000000000010000000000000003, txGasLimit = 0x10000000001, txTo = to, txValue = 0x8000000000000001000000000000000100000000000000010000000000000009, txData = ""}
|
||||
wideFields = "88ffffffffffffffff88fffffffffffffffe8d10000000000000000000000007908000000000000001000000000000000386010000000001943535353535353535353535353535353535353535a0800000000000000100000000000000010000000000000001000000000000000980c0"
|
||||
|
||||
txKey :: S.Secp256k1PrivateKey
|
||||
txKey = right $ S.mkPrivateKey $ BA.replicate 32 0x46
|
||||
|
||||
Reference in New Issue
Block a user