forked from secretnamebasis/simple-wallet
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathws.go
More file actions
345 lines (292 loc) · 9.34 KB
/
Copy pathws.go
File metadata and controls
345 lines (292 loc) · 9.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
package main
import (
"bytes"
"context"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"runtime"
"time"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget"
"github.com/civilware/epoch"
"github.com/creachadair/jrpc2"
"github.com/deroproject/derohe/cryptography/crypto"
"github.com/deroproject/derohe/rpc"
"github.com/deroproject/derohe/walletapi/xswd"
)
func xswdServer(port int) *xswd.XSWD {
// apps must ask for permission on initial connection
forceAsk := false // as a result, apps can ask 'always allow' on initial connection
noStore := []string{""} // specifiy which methods are not allowed to have their permissions stored
return xswd.NewXSWDServerWithPort(port, program.wallet,
forceAsk, noStore, xswdAppHandler, xswdRequestHandler,
)
}
// this component handles the application acceptance
// the wallet is receiving application data from a source,
// and then granting permission based on the data therein
func xswdAppHandler(data *xswd.ApplicationData) bool {
// reject all connection attempts while screen is locked.
if program.preferences.Bool("isLocked") {
return reject
}
// let's serve up the data
text := "\tID: \n" + data.Id + "\n" +
"\tNAME: " + data.Name + "\n" +
"\tDESCRIPTION: " + data.Description + "\n" +
"\tURL: " + data.Url + "\n"
// let's verify this real quick
address, message, err := program.wallet.CheckSignature([]byte(data.Signature))
if err != nil {
showError(fmt.Errorf("app authorization signature resulted in err:\n%s", err), program.window)
return reject
}
// fmt.Println(address.String(), string(message))
text += "\tDEVELOPER: \n" + address.String()
label := widget.NewLabel(text)
var msg []byte
msg, err = hex.DecodeString(string(message))
if err != nil {
showError(fmt.Errorf("app authorization message decoding resulted in err:\n%s", err), program.window)
return reject
}
id, err := hex.DecodeString(data.Id)
if err != nil {
showError(fmt.Errorf("app authorization data.Id resulted in err:\n%s", err), program.window)
return reject
}
if !bytes.Equal(msg, id) {
// showError(errors.New("application signature does not match app id"), program.window)
return reject
}
sig := widget.NewLabel("✅APP SIGNATURE MATCH✅")
sig.Alignment = fyne.TextAlignCenter
// range through the permissions if any
app := container.NewVBox(label, sig)
content := container.NewBorder(app, nil, nil, nil)
if len(data.Permissions) > 0 {
app.Add(container.NewCenter(widget.NewLabel(
" ✋⚠️ APP PERMISSIONS REQUESTS ⚠️✋\n" +
"this application is asking for these permissions:")))
permit := ""
for permission, request := range data.Permissions {
permit += "❓ " + permission + ": " + request.String() + "\n"
}
p := widget.NewLabel(permit)
p.Alignment = fyne.TextAlignCenter
content.Add(container.NewScroll(p))
}
// we are going to wait on a choice
choice := make(chan bool)
// create a callback function
callback := func(b bool) {
if b { // if they hit confirm, they have accepted
choice <- accept
} else { // otherwise... rejected
choice <- reject // default is to reject everything
}
}
// create a pop-up like dialog
pop := dialog.NewCustomConfirm(
"New WebSocket Request",
confirm, dismiss,
content, callback,
program.window,
)
pop.SetConfirmImportance(widget.WarningImportance)
// show it
pop.Resize(fyne.NewSize(program.size.Width/2, ((program.size.Height / 4) * 3)))
pop.Show()
fyne.DoAndWait(func() {
program.window.Show()
})
// and block (eg. wait) for the choice
return <-choice
}
// this is a method request that is extended to the underlying API
// we are going to make it as simple as it gets:
// do you allow it, do you reject it
func xswdRequestHandler(data *xswd.ApplicationData, r *jrpc2.Request) xswd.Permission {
// reject all connection attempts while screen is locked.
if program.preferences.Bool("isLocked") {
return xswd.Deny
}
// let's serve up some content
text := "\tID: \n" + data.Id + "\n" +
"\tNAME: " + data.Name + "\n" +
"\tDESCRIPTION: " + data.Description + "\n" +
"\tURL: " + data.Url + "\n"
// let's verify this real quick
address, message, err := program.wallet.CheckSignature([]byte(data.Signature))
if err != nil {
showError(fmt.Errorf("app request resulted in err:\n%s", err), program.window)
return xswd.Deny
}
// fmt.Println(address.String(), string(message))
text += "\tDEVELOPER: \n" + address.String()
label := widget.NewLabel(text)
msg, err := hex.DecodeString(string(message))
if err != nil {
showError(fmt.Errorf("app request resulted in err:\n%s", err), program.window)
return xswd.Deny
}
id, err := hex.DecodeString(data.Id)
if err != nil {
showError(fmt.Errorf("app request resulted in err:\n%s", err), program.window)
return xswd.Deny
}
if !bytes.Equal(msg, id) {
// showError(errors.New("application signature does not match app id"), program.window)
// don't bother user with bad requests
return xswd.AlwaysDeny
}
sig := widget.NewLabel("✅APP SIGNATURE MATCH✅")
sig.Alignment = fyne.TextAlignCenter
method := widget.NewLabel(`❓ METHOD REQUEST: ` + r.Method())
method.Alignment = fyne.TextAlignCenter
app := container.NewVBox(label, sig, method)
content := container.NewBorder(app, nil, nil, nil)
// if it has params, process them
if r.HasParams() {
// var params rpc.EventNotification
// un-marshal the params
// if err := r.UnmarshalParams(¶ms); err != nil {
// // if the params fail, serve the error
// showError(fmt.Errorf("app request resulted in err:\n%s", err), program.window)
// // // and then deny the request
// return xswd.Deny
// }
// add param string to the request
label := widget.NewLabel("")
switch r.Method() {
case "querykey":
// not implemented
break
case "scinvoke":
p := rpc.SC_Invoke_Params{}
if err := json.Unmarshal([]byte(r.ParamString()), &p); err != nil {
showError(fmt.Errorf("app request resulted in err:\n%s", err), program.window)
break
}
pretty, err := json.MarshalIndent(p, "", " ")
if err != nil {
showError(fmt.Errorf("app request resulted in err:\n%s", err), program.window)
break
}
label.SetText(string(pretty))
label.Wrapping = fyne.TextWrapWord
case "transfer":
p := rpc.Transfer_Params{}
if err := json.Unmarshal([]byte(r.ParamString()), &p); err != nil {
showError(fmt.Errorf("app request resulted in err:\n%s", err), program.window)
break
}
text := ""
if len(p.Transfers) != 0 {
text += "TRANSFERS:\n" + fmt.Sprintf("%v", p.Transfers) + "\n"
}
if p.SC_Code != "" {
text += "CODE:\n" + p.SC_Code + "\n"
}
if p.Fees != 0 {
text += "FEES: " + rpc.FormatMoney(p.Fees) + " DERO"
}
label.SetText(text)
label.Wrapping = fyne.TextWrapWord
default:
label.SetText(r.ParamString())
label.Wrapping = fyne.TextWrapBreak
}
scroll := container.NewScroll(label)
content.Add(scroll)
}
// we are going to wait for a choice
choice := make(chan bool)
// we are going to have
callback := func(b bool) {
if b { // if they say confirm, accept
choice <- accept
} else { // if they dismiss, reject
choice <- reject
}
}
// build a pop-up
pop := dialog.NewCustomConfirm(
"New WebSocket Request",
confirm, dismiss,
content, callback,
program.window,
)
pop.SetConfirmImportance(widget.DangerImportance)
// show it
pop.Resize(fyne.NewSize(program.size.Width/2, ((program.size.Height / 4) * 3)))
fyne.DoAndWait(func() {
pop.Show()
program.window.Show()
})
// now wait for the choice
if <-choice { // if accepted...
return xswd.Allow
}
// default is to deny
return xswd.Deny
}
type getAssetsResult struct {
SCIDS []string `json:"scids"`
}
func getAssets(ctx context.Context) (getAssetsResult, error) {
scids := []string{}
for _, each := range program.caches.assets {
scids = append(scids, each.hash)
}
return getAssetsResult{scids}, nil
}
type getAssetBalanceParams struct {
Height int64
SCID string
}
type getAssetBalanceResult struct {
Balance uint64 `json:"balance"`
}
func getAssetBalance(ctx context.Context, params getAssetBalanceParams) (getAssetBalanceResult, error) {
hash := crypto.HashHexToHash(params.SCID)
height := params.Height // -1 is current topoheight
address := program.wallet.GetAddress()
bal, _, err := program.wallet.GetDecryptedBalanceAtTopoHeight(hash, height, address.String())
if err != nil {
return getAssetBalanceResult{}, err
}
return getAssetBalanceResult{bal}, nil
}
type getAttemptEpochParams struct {
Hashes int `json:"hashes"`
Address string `json:"address"`
}
func attemptEPOCHWithAddr(ctx context.Context, params getAttemptEpochParams) (epoch.EPOCH_Result, error) {
reserve := 2 // one for the app and one for the os
threads := runtime.GOMAXPROCS(0)
maximum := threads - reserve
epoch.SetMaxThreads(maximum)
addr, err := rpc.NewAddress(params.Address)
if err != nil {
return epoch.EPOCH_Result{}, errors.New("invalid address")
}
endpoint := program.node.current
err = epoch.StartGetWork(addr.String(), endpoint)
if err != nil {
return epoch.EPOCH_Result{}, errors.New("failed start get work server")
}
defer epoch.StopGetWork()
timeout := time.Second * 10
err = epoch.JobIsReady(timeout)
if err != nil {
return epoch.EPOCH_Result{}, errors.New("failed get job before timeout")
}
// the smaller of the two
hashes := min(params.Hashes, epoch.LIMIT_MAX_HASHES)
return epoch.AttemptHashes(hashes)
}