rules.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. // Copyright 2018 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // The go-ethereum library is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. package rules
  17. import (
  18. "encoding/json"
  19. "fmt"
  20. "os"
  21. "strings"
  22. "github.com/dop251/goja"
  23. "github.com/ethereum/go-ethereum/internal/ethapi"
  24. "github.com/ethereum/go-ethereum/log"
  25. "github.com/ethereum/go-ethereum/signer/core"
  26. "github.com/ethereum/go-ethereum/signer/rules/deps"
  27. "github.com/ethereum/go-ethereum/signer/storage"
  28. )
  29. var (
  30. BigNumber_JS = deps.MustAsset("bignumber.js")
  31. )
  32. // consoleOutput is an override for the console.log and console.error methods to
  33. // stream the output into the configured output stream instead of stdout.
  34. func consoleOutput(call goja.FunctionCall) goja.Value {
  35. output := []string{"JS:> "}
  36. for _, argument := range call.Arguments {
  37. output = append(output, fmt.Sprintf("%v", argument))
  38. }
  39. fmt.Fprintln(os.Stderr, strings.Join(output, " "))
  40. return goja.Undefined()
  41. }
  42. // rulesetUI provides an implementation of UIClientAPI that evaluates a javascript
  43. // file for each defined UI-method
  44. type rulesetUI struct {
  45. next core.UIClientAPI // The next handler, for manual processing
  46. storage storage.Storage
  47. jsRules string // The rules to use
  48. }
  49. func NewRuleEvaluator(next core.UIClientAPI, jsbackend storage.Storage) (*rulesetUI, error) {
  50. c := &rulesetUI{
  51. next: next,
  52. storage: jsbackend,
  53. jsRules: "",
  54. }
  55. return c, nil
  56. }
  57. func (r *rulesetUI) RegisterUIServer(api *core.UIServerAPI) {
  58. // TODO, make it possible to query from js
  59. }
  60. func (r *rulesetUI) Init(javascriptRules string) error {
  61. r.jsRules = javascriptRules
  62. return nil
  63. }
  64. func (r *rulesetUI) execute(jsfunc string, jsarg interface{}) (goja.Value, error) {
  65. // Instantiate a fresh vm engine every time
  66. vm := goja.New()
  67. // Set the native callbacks
  68. consoleObj := vm.NewObject()
  69. consoleObj.Set("log", consoleOutput)
  70. consoleObj.Set("error", consoleOutput)
  71. vm.Set("console", consoleObj)
  72. storageObj := vm.NewObject()
  73. storageObj.Set("put", func(call goja.FunctionCall) goja.Value {
  74. key, val := call.Argument(0).String(), call.Argument(1).String()
  75. if val == "" {
  76. r.storage.Del(key)
  77. } else {
  78. r.storage.Put(key, val)
  79. }
  80. return goja.Null()
  81. })
  82. storageObj.Set("get", func(call goja.FunctionCall) goja.Value {
  83. goval, _ := r.storage.Get(call.Argument(0).String())
  84. jsval := vm.ToValue(goval)
  85. return jsval
  86. })
  87. vm.Set("storage", storageObj)
  88. // Load bootstrap libraries
  89. script, err := goja.Compile("bignumber.js", string(BigNumber_JS), true)
  90. if err != nil {
  91. log.Warn("Failed loading libraries", "err", err)
  92. return goja.Undefined(), err
  93. }
  94. vm.RunProgram(script)
  95. // Run the actual rule implementation
  96. _, err = vm.RunString(r.jsRules)
  97. if err != nil {
  98. log.Warn("Execution failed", "err", err)
  99. return goja.Undefined(), err
  100. }
  101. // And the actual call
  102. // All calls are objects with the parameters being keys in that object.
  103. // To provide additional insulation between js and go, we serialize it into JSON on the Go-side,
  104. // and deserialize it on the JS side.
  105. jsonbytes, err := json.Marshal(jsarg)
  106. if err != nil {
  107. log.Warn("failed marshalling data", "data", jsarg)
  108. return goja.Undefined(), err
  109. }
  110. // Now, we call foobar(JSON.parse(<jsondata>)).
  111. var call string
  112. if len(jsonbytes) > 0 {
  113. call = fmt.Sprintf("%v(JSON.parse(%v))", jsfunc, string(jsonbytes))
  114. } else {
  115. call = fmt.Sprintf("%v()", jsfunc)
  116. }
  117. return vm.RunString(call)
  118. }
  119. func (r *rulesetUI) checkApproval(jsfunc string, jsarg []byte, err error) (bool, error) {
  120. if err != nil {
  121. return false, err
  122. }
  123. v, err := r.execute(jsfunc, string(jsarg))
  124. if err != nil {
  125. log.Info("error occurred during execution", "error", err)
  126. return false, err
  127. }
  128. result := v.ToString().String()
  129. if result == "Approve" {
  130. log.Info("Op approved")
  131. return true, nil
  132. } else if result == "Reject" {
  133. log.Info("Op rejected")
  134. return false, nil
  135. }
  136. return false, fmt.Errorf("unknown response")
  137. }
  138. func (r *rulesetUI) ApproveTx(request *core.SignTxRequest) (core.SignTxResponse, error) {
  139. jsonreq, err := json.Marshal(request)
  140. approved, err := r.checkApproval("ApproveTx", jsonreq, err)
  141. if err != nil {
  142. log.Info("Rule-based approval error, going to manual", "error", err)
  143. return r.next.ApproveTx(request)
  144. }
  145. if approved {
  146. return core.SignTxResponse{
  147. Transaction: request.Transaction,
  148. Approved: true},
  149. nil
  150. }
  151. return core.SignTxResponse{Approved: false}, err
  152. }
  153. func (r *rulesetUI) ApproveSignData(request *core.SignDataRequest) (core.SignDataResponse, error) {
  154. jsonreq, err := json.Marshal(request)
  155. approved, err := r.checkApproval("ApproveSignData", jsonreq, err)
  156. if err != nil {
  157. log.Info("Rule-based approval error, going to manual", "error", err)
  158. return r.next.ApproveSignData(request)
  159. }
  160. if approved {
  161. return core.SignDataResponse{Approved: true}, nil
  162. }
  163. return core.SignDataResponse{Approved: false}, err
  164. }
  165. // OnInputRequired not handled by rules
  166. func (r *rulesetUI) OnInputRequired(info core.UserInputRequest) (core.UserInputResponse, error) {
  167. return r.next.OnInputRequired(info)
  168. }
  169. func (r *rulesetUI) ApproveListing(request *core.ListRequest) (core.ListResponse, error) {
  170. jsonreq, err := json.Marshal(request)
  171. approved, err := r.checkApproval("ApproveListing", jsonreq, err)
  172. if err != nil {
  173. log.Info("Rule-based approval error, going to manual", "error", err)
  174. return r.next.ApproveListing(request)
  175. }
  176. if approved {
  177. return core.ListResponse{Accounts: request.Accounts}, nil
  178. }
  179. return core.ListResponse{}, err
  180. }
  181. func (r *rulesetUI) ApproveNewAccount(request *core.NewAccountRequest) (core.NewAccountResponse, error) {
  182. // This cannot be handled by rules, requires setting a password
  183. // dispatch to next
  184. return r.next.ApproveNewAccount(request)
  185. }
  186. func (r *rulesetUI) ShowError(message string) {
  187. log.Error(message)
  188. r.next.ShowError(message)
  189. }
  190. func (r *rulesetUI) ShowInfo(message string) {
  191. log.Info(message)
  192. r.next.ShowInfo(message)
  193. }
  194. func (r *rulesetUI) OnSignerStartup(info core.StartupInfo) {
  195. jsonInfo, err := json.Marshal(info)
  196. if err != nil {
  197. log.Warn("failed marshalling data", "data", info)
  198. return
  199. }
  200. r.next.OnSignerStartup(info)
  201. _, err = r.execute("OnSignerStartup", string(jsonInfo))
  202. if err != nil {
  203. log.Info("error occurred during execution", "error", err)
  204. }
  205. }
  206. func (r *rulesetUI) OnApprovedTx(tx ethapi.SignTransactionResult) {
  207. jsonTx, err := json.Marshal(tx)
  208. if err != nil {
  209. log.Warn("failed marshalling transaction", "tx", tx)
  210. return
  211. }
  212. _, err = r.execute("OnApprovedTx", string(jsonTx))
  213. if err != nil {
  214. log.Info("error occurred during execution", "error", err)
  215. }
  216. }