Skip to content

Commit 42d1b7f

Browse files
committed
feat(SOLNENG-27): here we go
1 parent 53e8f1d commit 42d1b7f

File tree

5 files changed

+87
-99
lines changed

5 files changed

+87
-99
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
*.crt
22
*.key
3-
.vscode
3+
.vscode
4+
init

README.md

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,54 @@ sequenceDiagram
88
participant StakeClient as Sample stake<br> client application
99
participant StakeAPI as Stake Intent API
1010
participant Blockchain as Blockchain
11-
participant TSM as Sender Wallet Vault<br>(private key)
11+
box Builder Vault
12+
participant TSM1 as MPC Wallet <br>(private key share 1)
13+
participant TSM2 as MPC Wallet <br>(private key share 2)
14+
participant TSM3 as MPC Wallet <br>(private key share 3)
15+
end
1216
1317
StakeClient ->> StakeAPI: get StakeIntent unsigned tx data <br>(amount, withdrawal & recipient address)
1418
StakeClient ->> Blockchain: get blockchain inputs for new tx<br>(gas fee, chainID, sender wallet nonce)
1519
StakeClient ->> StakeClient: construct unsigned tx
16-
StakeClient ->> TSM: request signature of unsigned tx
17-
TSM -->> StakeClient: return tx signature
20+
StakeClient ->> TSM1: request signature of unsigned tx
21+
TSM1 -->> StakeClient: return partial signature
22+
StakeClient ->> TSM2: request signature of unsigned tx
23+
TSM2 -->> StakeClient: return partial signature
24+
StakeClient ->> TSM3: request signature of unsigned tx
25+
TSM3 -->> StakeClient: return partial signature
26+
StakeClient ->> StakeClient: combine partial signatures
1827
StakeClient ->> Blockchain: broadcast signed tx<br>(signed tx, deposit contract)
1928
```
2029

21-
2230
<!--
23-
- ToDo:
24-
- use AWS Builder Vault environment (remove all code needed for mTLS with BDApp Builder Vault)
2531
2632
# Get Plans
27-
http -b GET https://svc.blockdaemon.com/boss/v1/plans?protocols=ethereum&networks=holesky \
28-
X-API-KEY:$XAPIKEY \
29-
X-Client-ID:demo-organization
33+
http -b GET https://svc.blockdaemon.com/boss/v1/plans \
34+
X-API-KEY:$STAKE_API_KEY \
3035
3136
# Post Intent
3237
http -b POST https://svc.blockdaemon.com/boss/v1/ethereum/holesky/stake-intents \
33-
X-API-KEY:$XAPIKEY \
38+
X-API-KEY:$STAKE_API_KEY \
3439
accept:application/json \
3540
content-type:application/json \
3641
stakes:='[{"amount":"32000000000","withdrawal_address":"0x00000000219ab540356cBB839Cbe05303d7705Fa","fee_recipient":"0x93247f2209abcacf57b75a51dafae777f9dd38bc"}]'
3742
3843
# Get Intents
39-
http GET https://svc.blockdaemon.com/boss/v1/stake-intents?protocols=ethereum&networks=holesky \
40-
X-API-KEY:$XAPIKEY
44+
http -b GET https://svc.blockdaemon.com/boss/v1/stake-intents?protocols=ethereum&networks=holesky \
45+
X-API-KEY:$STAKE_API_KEY
4146
```
47+
48+
sequenceDiagram
49+
autonumber
50+
participant StakeClient as Sample stake<br> client application
51+
participant StakeAPI as Stake Intent API
52+
participant Blockchain as Blockchain
53+
participant TSM as Sender Wallet Vault<br>(private key)
54+
55+
StakeClient ->> StakeAPI: get StakeIntent unsigned tx data <br>(amount, withdrawal & recipient address)
56+
StakeClient ->> Blockchain: get blockchain inputs for new tx<br>(gas fee, chainID, sender wallet nonce)
57+
StakeClient ->> StakeClient: construct unsigned tx
58+
StakeClient ->> TSM: request signature of unsigned tx
59+
TSM ->> StakeClient: return tx signature
60+
StakeClient ->> Blockchain: broadcast signed tx<br>(signed tx, deposit contract)
4261
--!>

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.21.0
44

55
require (
66
github.com/ethereum/go-ethereum v1.13.14
7+
github.com/fatih/structs v1.1.0
78
gitlab.com/Blockdaemon/go-tsm-sdkv2 v0.0.0-20240227205306-65d60d978f8d
89
golang.org/x/sync v0.6.0
910
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R
5252
github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
5353
github.com/ethereum/go-ethereum v1.13.14 h1:EwiY3FZP94derMCIam1iW4HFVrSgIcpsu0HwTQtm6CQ=
5454
github.com/ethereum/go-ethereum v1.13.14/go.mod h1:TN8ZiHrdJwSe8Cb6x+p0hs5CxhJZPbqB7hHkaUXcmIU=
55+
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
56+
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
5557
github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA=
5658
github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
5759
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=

main.go

Lines changed: 51 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ package main
33
import (
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

2825
type Stake struct {
2926
WithdrawalAddress string `json:"withdrawal_address"`
@@ -52,12 +49,14 @@ type Response struct {
5249
// ! Create stake intent
5350
func 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("\nStake 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("\nStake 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("\nCrafted 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-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaWLFxRxgLQHJ662gcd2LfPFYKDmI\n8AlzFUu/MFR0Pb5d0JYSBL/HAUR5/1OXfEV18riJZJCeOa1gxNocwzqZ9Q==\n-----END PUBLIC KEY-----\n",
135-
1: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErPzIZwRgiFpBgIDYCzfRxEgvasus\nHa4qlwWnJ0TnlGgjcfD5Bp40J9HnOdlBkzhtVWq5PiLEMaFWdApTkRBT9Q==\n-----END PUBLIC KEY-----\n",
136-
2: "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyaLwUY4A99EDvqGMjBT2Q/M3zydm\nOniFOZicnwdvnJTMgXw8LAqLee+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("\nTransaction 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("\nSigned 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
248213
func 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("\nTransaction hash:", txHash)
289254
}

0 commit comments

Comments
 (0)