@@ -3,10 +3,7 @@ package main
33import (
44 "bytes"
55 "context"
6- "crypto/x509"
7- "encoding/base64"
86 "encoding/json"
9- "encoding/pem"
107 "fmt"
118 "log"
129 "math/big"
@@ -21,9 +18,9 @@ import (
2118 "github.com/ethereum/go-ethereum/rpc"
2219 "gitlab.com/Blockdaemon/go-tsm-sdkv2/tsm"
2320 "golang.org/x/sync/errgroup"
24- )
2521
26- var gwei = new (big.Int ).Exp (big .NewInt (10 ), big .NewInt (9 ), nil )
22+ "github.com/fatih/structs"
23+ )
2724
2825type Stake struct {
2926 WithdrawalAddress string `json:"withdrawal_address"`
@@ -52,12 +49,14 @@ type Response struct {
5249// ! Create stake intent
5350func createStakeIntent (stakeApiKey string , stakeRequest * Request ) (string , string , * big.Int ) {
5451 requestJson , _ := json .Marshal (stakeRequest )
55- fmt .Println ("Stake request json:" , stakeRequest )
52+
53+ fmt .Println ("\n Stake request:\n " , structs .Map (stakeRequest ))
5654
5755 req , _ := http .NewRequest ("POST" , "https://svc.blockdaemon.com/boss/v1/ethereum/holesky/stake-intents" , bytes .NewBuffer (requestJson ))
5856 req .Header .Set ("Content-Type" , "application/json" )
5957 req .Header .Set ("Accept" , "application/json" )
6058 req .Header .Set ("X-API-Key" , stakeApiKey )
59+ //req.Header.Set("Idempotency-Key", "E96E9CE5-A81E-4178-AAA7-4BDC7ED1BCC2") // ToDo - remove for demos
6160
6261 client := & http.Client {}
6362 resp , err := client .Do (req )
@@ -77,14 +76,16 @@ func createStakeIntent(stakeApiKey string, stakeRequest *Request) (string, strin
7776 totalAmount := new (big.Int )
7877 for _ , stake := range stakes {
7978 amount , _ := new (big.Int ).SetString (stake .Amount , 10 )
79+ amount = amount .Mul (amount , new (big.Int ).SetInt64 (1000000000 ))
8080 totalAmount .Add (totalAmount , amount )
8181 }
82+ fmt .Println ("\n Stake response:\n " , structs .Map (stakeResponse ))
8283
8384 return stakeResponse .Ethereum .UnsignedTransaction , stakeResponse .Ethereum .ContractAddress , totalAmount
8485}
8586
8687// ! Craft transaction
87- func craftTx (client * ethclient.Client , ethereumSenderAddress string , contractAddress string , totalAmount * big.Int , txData string ) (* types.Transaction , * big.Int ) {
88+ func craftTx (client * ethclient.Client , ethereumSenderAddress string , contractAddress string , totalAmount * big.Int , txData string ) (* types.Transaction , [] byte , * big.Int ) {
8889 nonce , err := client .PendingNonceAt (context .Background (), common .HexToAddress (ethereumSenderAddress ))
8990 if err != nil {
9091 log .Fatal (err )
@@ -107,98 +108,61 @@ func craftTx(client *ethclient.Client, ethereumSenderAddress string, contractAdd
107108 To : & decodedContactAddress ,
108109 GasPrice : gasPrice ,
109110 Value : totalAmount ,
110- Data : common .Hex2Bytes (txData ),
111+ Data : common .FromHex (txData ),
111112 }
113+
112114 gasLimit , err := client .EstimateGas (context .Background (), msg )
113115 if err != nil {
114116 log .Fatal (err )
115117 }
116118
117- unsignedTx := types .NewTx (& types.DynamicFeeTx {
118- ChainID : chainID ,
119- Nonce : nonce ,
120- To : & decodedContactAddress ,
121- Value : totalAmount ,
122- Gas : gasLimit ,
123- Data : common .Hex2Bytes (txData ),
119+ unsignedTx := types .NewTx (& types.LegacyTx {
120+ Nonce : nonce ,
121+ To : & decodedContactAddress ,
122+ Value : totalAmount ,
123+ Gas : gasLimit ,
124+ GasPrice : gasPrice ,
125+ Data : common .FromHex (txData ),
124126 })
125127
126- return unsignedTx , chainID
127- }
128-
129- // ! Sign transaction
130- func signTx (unsignedTx * types.Transaction ) []byte {
128+ fmt .Println ("\n Crafted unsigned transaction:\n " , "Nonce:" , unsignedTx .Nonce (), "\n GasPrice:" , unsignedTx .GasPrice (), "\n Gas:" , unsignedTx .Gas (), "\n To:" , unsignedTx .To ().String (), "\n Value:" , unsignedTx .Value ())
131129
132- // Builder Vault server TLS public keys (self-signed)
133- var serverMtlsPublicKeys = map [int ]string {
134- 0 : "-----BEGIN PUBLIC KEY-----\n MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaWLFxRxgLQHJ662gcd2LfPFYKDmI\n 8AlzFUu/MFR0Pb5d0JYSBL/HAUR5/1OXfEV18riJZJCeOa1gxNocwzqZ9Q==\n -----END PUBLIC KEY-----\n " ,
135- 1 : "-----BEGIN PUBLIC KEY-----\n MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErPzIZwRgiFpBgIDYCzfRxEgvasus\n Ha4qlwWnJ0TnlGgjcfD5Bp40J9HnOdlBkzhtVWq5PiLEMaFWdApTkRBT9Q==\n -----END PUBLIC KEY-----\n " ,
136- 2 : "-----BEGIN PUBLIC KEY-----\n MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyaLwUY4A99EDvqGMjBT2Q/M3zydm\n OniFOZicnwdvnJTMgXw8LAqLee+0VFIUZbxRPTvN1c1ORoD8+2xJ0VPglg==\n -----END PUBLIC KEY-----\n " ,
137- }
130+ signer := types .NewEIP155Signer (chainID )
138131
139- // Decode server public keys to bytes for use in TLS client authentication
140- serverPKIXPublicKeys := make ([][]byte , len (serverMtlsPublicKeys ))
141- for i := range serverMtlsPublicKeys {
142- block , rest := pem .Decode ([]byte (serverMtlsPublicKeys [i ]))
143- if block == nil || len (rest ) != 0 {
144- panic ("error decoding server public key (no block data)" )
145- }
146- serverPublicKey , err := x509 .ParsePKIXPublicKey (block .Bytes )
147- if err != nil {
148- panic (err )
149- }
150- serverPKIXPublicKeys [i ], err = x509 .MarshalPKIXPublicKey (serverPublicKey )
151- if err != nil {
152- panic (err )
153- }
154- }
132+ return unsignedTx , signer .Hash (unsignedTx ).Bytes (), chainID
133+ }
155134
156- // Create TSM SDK clients with mTLS authentication and public key pinning
157- clients := make ([]* tsm.Client , len (serverMtlsPublicKeys ))
158- for i := range clients {
159- config , err := tsm.Configuration {URL : fmt .Sprintf ("https://tsm-sandbox.prd.wallet.blockdaemon.app:%v" , 8080 + i )}.WithMTLSAuthentication ("../client.key" , "../client.crt" , serverPKIXPublicKeys [i ])
160- if err != nil {
161- panic (err )
162- }
163- clients [i ], err = tsm .NewClient (config )
164- if err != nil {
165- panic (err )
166- }
167- }
135+ // ! Sign transaction
136+ func signTx (unsignedTxHash []byte ) []byte {
137+ // Create clients for each of the nodes
168138
169- // The public keys of the other players to encrypt MPC protocol data end-to-end
170- playerB64Pubkeys := []string {
171- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtDFBfanInAMHNKKDG2RW/DiSnYeI7scVvfHIwUIRdbPH0gBrsilqxlvsKZTakN8om/Psc6igO+224X8T0J9eMg==" ,
172- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqvSkhonTeNhlETse8v3X7g4p100EW9xIqg4aRpD8yDXgB0UYjhd+gFtOCsRT2lRhuqNForqqC+YnBsJeZ4ANxg==" ,
173- "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEBaHCIiViexaVaPuER4tE6oJE3IBA0U//GlB51C1kXkT07liVc51uWuYk78wi4e1unxC95QbeIfnDCG2i43fW3g==" ,
139+ configs := []* tsm.Configuration {
140+ tsm.Configuration {URL : "https://node-1-prod.tsm-test-greg.dev.wallet.blockdaemon.app" }.WithAPIKeyAuthentication (os .Getenv ("BV_NODE1_KEY" )),
141+ tsm.Configuration {URL : "https://node-2-prod.tsm-test-greg.dev.wallet.blockdaemon.app" }.WithAPIKeyAuthentication (os .Getenv ("BV_NODE2_KEY" )),
174142 }
175143
176- playerPubkeys := map [int ][]byte {}
177- playerIds := []int {0 , 1 , 2 }
178- // iterate over other players public keys and convert them
179- for i := range playerIds {
180- pubkey , err := base64 .StdEncoding .DecodeString (playerB64Pubkeys [i ])
181- if err != nil {
144+ clients := make ([]* tsm.Client , len (configs ))
145+ for i , config := range configs {
146+ var err error
147+ if clients [i ], err = tsm .NewClient (config ); err != nil {
182148 panic (err )
183149 }
184- playerPubkeys [playerIds [i ]] = pubkey
185150 }
186151
187- // Use the TSM to sign via the existing derived key for chain m/44/1
188- masterKeyID := "JWEcnWmbdncdBOCpMyRW4EldUZyL "
189- chainPath := []uint32 {44 , 551 , 0 , 0 }
152+ // Use the TSM to sign via the existing derived key for chain m/44/60
153+ keyID := "r6mMzrh85Oel0QvpT0VsXdKJs9A4 "
154+ derivationPath := []uint32 {44 , 60 , 0 , 0 }
190155
191156 partialSignaturesLock := sync.Mutex {}
192- partialSignatures := make ([][]byte , 0 )
193- //sessionConfig := tsm.NewStaticSessionConfig(tsm.GenerateSessionID(),3)
194- Players := []int {0 , 1 , 2 }
195- sessionConfig := tsm .NewSessionConfig (tsm .GenerateSessionID (), Players , playerPubkeys )
157+ var partialSignatures [][]byte
158+ signPlayers := []int {1 , 2 }
159+ signSessionConfig := tsm .NewSessionConfig (tsm .GenerateSessionID (), signPlayers , nil )
196160 ctx := context .Background ()
197161 var eg errgroup.Group
198162 for _ , client := range clients {
199163 client := client
200164 eg .Go (func () error {
201- partialSignResult , err := client .ECDSA ().Sign (ctx , sessionConfig , masterKeyID , chainPath , unsignedTx . Hash (). Bytes () )
165+ partialSignResult , err := client .ECDSA ().Sign (ctx , signSessionConfig , keyID , derivationPath , unsignedTxHash )
202166 if err != nil {
203167 return err
204168 }
@@ -213,18 +177,18 @@ func signTx(unsignedTx *types.Transaction) []byte {
213177 panic (err )
214178 }
215179
216- signature , err := tsm .ECDSAFinalizeSignature (unsignedTx . Hash (). Bytes () , partialSignatures )
180+ signature , err := tsm .ECDSAFinalizeSignature (unsignedTxHash , partialSignatures )
217181 if err != nil {
218182 panic (err )
219183 }
220184
221- // Add signature to transaction
185+ // Construct Ethereum V R S signature format
222186
223187 sigBytes := make ([]byte , 2 * 32 + 1 )
224188 copy (sigBytes [0 :32 ], signature .R ())
225189 copy (sigBytes [32 :64 ], signature .S ())
226190 sigBytes [64 ] = byte (signature .RecoveryID ())
227- fmt .Println ("Signed transaction: " , sigBytes )
191+ fmt .Println ("\n Transaction signature: \n " , sigBytes )
228192
229193 return sigBytes
230194}
@@ -236,6 +200,7 @@ func sendTx(client *ethclient.Client, chainID *big.Int, unsignedTx *types.Transa
236200 if err != nil {
237201 panic (err )
238202 }
203+ fmt .Println ("\n Signed raw transaction:\n " , signedTx .Data ())
239204
240205 err = client .SendTransaction (context .Background (), signedTx )
241206 if err != nil {
@@ -248,9 +213,9 @@ func sendTx(client *ethclient.Client, chainID *big.Int, unsignedTx *types.Transa
248213func main () {
249214 stakeApiKey := os .Getenv ("STAKE_API_KEY" )
250215 rpcApiKey := os .Getenv ("RPC_API_KEY" )
251- ethereumSenderAddress := "0x5C7168F2D1243A75B5970a9d0bBDBDFD8836eb2f " // Set your Ethereum sender address here. E.g. "0x71Bff5FFeF6408dAe06c055caB770D76E04831d2"
252- stakeWithdrawalAddress := "0x5C7168F2D1243A75B5970a9d0bBDBDFD8836eb2f "
253- stakeFeeRecipientAddress := "0x5C7168F2D1243A75B5970a9d0bBDBDFD8836eb2f "
216+ ethereumSenderAddress := "0xE8fE1C1058b34d5152f2B23908dD8c65715F2D3A " // Set your Ethereum sender address here. E.g. "0x71Bff5FFeF6408dAe06c055caB770D76E04831d2"
217+ stakeWithdrawalAddress := "0xE8fE1C1058b34d5152f2B23908dD8c65715F2D3A "
218+ stakeFeeRecipientAddress := "0xE8fE1C1058b34d5152f2B23908dD8c65715F2D3A "
254219
255220 // Create go-ethereum/rpc client with heaader-based authentication
256221 rpcClient , err := rpc .Dial ("https://svc.blockdaemon.com/ethereum/holesky/native" )
@@ -265,7 +230,7 @@ func main() {
265230 if err != nil {
266231 panic (err )
267232 }
268- fmt .Println ("Balance at account:" , ethereumSenderAddress , "=" , balance . Int64 ( ), "wei" )
233+ fmt .Println ("Balance at account:" , ethereumSenderAddress , "=" , ( balance ), "wei" )
269234
270235 // Define stake intent request: 1x32ETH
271236 stakeRequest := & Request {
@@ -279,11 +244,11 @@ func main() {
279244 }
280245
281246 txData , contractAddress , totalAmount := createStakeIntent (stakeApiKey , stakeRequest )
282- totalAmount .Mul (totalAmount , gwei )
283247
284- unsignedTx , chainID := craftTx (client , ethereumSenderAddress , contractAddress , totalAmount , txData )
285- signature := signTx ( unsignedTx )
286- txHash := sendTx ( client , chainID , unsignedTx , signature )
248+ unsignedTx , unsignedTxHash , chainID := craftTx (client , ethereumSenderAddress , contractAddress , totalAmount , txData )
249+
250+ signature := signTx ( unsignedTxHash )
287251
288- fmt .Println (txHash )
252+ txHash := sendTx (client , chainID , unsignedTx , signature )
253+ fmt .Println ("\n Transaction hash:" , txHash )
289254}
0 commit comments