ethers-io / ethers.js

Complete Ethereum library and wallet implementation in JavaScript.
https://ethers.org/
MIT License
7.94k stars 1.85k forks source link

ethers.js with React Native 0.63? #1070

Closed emclab closed 4 years ago

emclab commented 4 years ago

I am new to ethers.js and just read a online post about it. I am looking for a lib (such as web3.js) which can be integrated with React Native 0.63 app and providing features of ethereum wallet, performing transaction, such as invoking smart contract methods , transferring fund and viewing transactions on chain. The post I just read claims that ethers.js supports React Native by default. I am excited about it as I have tried to use rn-nodeify for ipfs module on RN app and could not solve a few bundle issue. What is the procedure of adding ethers.js to a React Native 0.63 app? Any documents or online posts would help. Thanks.

ricmoo commented 4 years ago

I think this should be all you need to get started:

https://docs.ethers.io/v5/cookbook/react-native/

If you have any problems, or suggestions to improve the docs, please let me know. :)

emclab commented 4 years ago

ricmoo, the doc is a good starting point for me. Many thanks.

In the doc, it specifically mentions module react native crypto. I used [react-native-crypto](https://www.npmjs.com/package/react-native-crypto) before. Is it good to fit in here? What kind of shim do I need for react-native-crypto if it is? Or it can go without shim in react native.

ricmoo commented 4 years ago

I’m not terribly familiar with RN other than what I tried out to get the RN test harness and docs together, but the main thing that a package like that is required for is the window.crypto.randomBytes. If that package exposes that, you’re golden. :)

Otherwise, if does mean Wallet.createRandom() is using an insecure random number source. If you are not creating random numbers in your dApp though (i.e. you only import existing mnemonics for wallets or use reliable random sources to create random wallets) you should be a-ok too.

ricmoo commented 4 years ago

(I just checked that package on npm, and I did try it out. It does expose a randomBytes, but when I dove into the iOS source code a bit deeper it does so in a way that may be vulnerable to local scripts sniffing the internal workings; just something to keep in mind)

emclab commented 4 years ago

I see. I need investigate further about that. I used [rn-nodeify](https://www.npmjs.com/package/rn-nodeify) to provide shim for some node core, including crypto.

emclab commented 4 years ago

ricmoo, is there article about the local scripts sniffing the internal workings on IOS? I did some online search and did not find much about it. I may not use the right key words. Thanks.

ricmoo commented 4 years ago

I don’t think so. It's more or less just a term I made up to describe the case where another script (or library installed via npm) running on the same host (i.e. phone) can sniff/access the initial seed they use for the synchronous random values and replace reading random numbers to generate the same sequence.

To get an idea of what I mean, just check out how it works here. Anything running in the same RN process has access to this and so it can “sniff” the seed...

I don’t really understand why they do this since iOS support synchronous random bytes in a way that is compatible with the native RN hooks, but I could not find any library the correctly implemented this and don’t really want to maintain a react native native module myself. :)

emclab commented 4 years ago

I just came across this module [react-native-securerandom](https://www.npmjs.com/package/react-native-securerandom) which is more actively maintained and gaining popularity. This module does not require shim of rn-nodeify but just a few changes in setup file for both IOS and Android. Is it a good fit for ethers?

ricmoo commented 4 years ago

Yes, that library looks perfect! You will have to shim the crypto.getRandomValues though, and the import order of the root RN seems to be deferred, so you will likely need to create the shim as an import. But you could do something like:

import { generateSecureRandom } from 'react-native-securerandom';

window.crypto.getRandomValues = function(array) {
    console.log("Remove this later; just checking to make sure this is used");
    const random = generateSecureRandom(array.length);
    for (let i = 0; i < array.length; i++) { array[i] = random[i]; }
};

So,put that in some file and import it before the shims, and then try ethers.Wallet.createRandom() and make sure that console.log shows up in the output. If it does, you are golden.

If it works let me know (along with any other tweaks) and I'll add it to the RN docs for ethers. :)

ricmoo commented 4 years ago

(actually, it might make sense to ping the author of that library to offer a built-in shim)

emclab commented 4 years ago

ricmoo, thank you for the details. I just installed the ethers and is in the process of getting it started. Will update.

emclab commented 4 years ago

A shim.js was created under root of app for the code above and import "./shim" before import "@ethersproject/shims" in App.js. Here is an error about window.crypto:

[Wed Sep 30 2020 15:09:32.565]  BUNDLE  ./index.js
[Wed Sep 30 2020 15:10:00.313]  ERROR    TypeError: undefined is not an object (evaluating 'window.crypto')
[Wed Sep 30 2020 15:10:00.316]  ERROR    Invariant Violation: Module AppRegistry is not a registered callable module (calling runApplication)
[Wed Sep 30 2020 15:10:00.317]  ERROR    Invariant Violation: Module AppRegistry is not a registered callable module (calling runApplication)
ricmoo commented 4 years ago

Oh, try adding:

if (window.crypto == null) { window.crypto = {}; }

before setting the function.

emclab commented 4 years ago

Bingo & well done! Here is the output:

info Reloading app...
[Wed Sep 30 2020 15:24:51.568]  BUNDLE  ./index.js

[Wed Sep 30 2020 15:25:28.701]  LOG      Shims Injected:
[Wed Sep 30 2020 15:25:28.705]  LOG        - atob
[Wed Sep 30 2020 15:25:28.729]  LOG        - btoa
[Wed Sep 30 2020 15:25:28.731]  LOG        - nextTick
[Wed Sep 30 2020 15:25:28.732]  LOG        - FileReader.prototype.readAsArrayBuffer
[Wed Sep 30 2020 15:25:28.734]  LOG      Running "aniPinch" with {"rootTag":81}
[Wed Sep 30 2020 15:25:28.749]  LOG      Remove this later; just checking to make sure this is used
emclab commented 4 years ago

Here is the shim.js for output above:

import { generateSecureRandom } from 'react-native-securerandom';

if (window.crypto == null) window.crypto = {}; 
window.crypto.getRandomValues = function(array) {
    console.log("Remove this later; just checking to make sure this is used");
    const random = generateSecureRandom(array.length);
    for (let i = 0; i < array.length; i++) { array[i] = random[i]; }
};
ricmoo commented 4 years ago

That’s awesome. I can open an issue upstream to see if the author of that library would like to incorporate it. Unless you want to.

I will update the docs with this lib too. Good find. :)

emclab commented 4 years ago

Great! go ahead with pull request. If you need me to followup with it, please let me know. I would be happy to help. Many thanks.

ricmoo commented 4 years ago

Actually, can you double check it works?

What happens if you use ethers.utils.getRandomBytes(32) after setting the shim. It looks like it may only return a promise and not the actual random data synchronously... :s

emclab commented 4 years ago

Got some error [20:38:20] E | ReactNativeJS ▶︎ TypeError: _ethers.ethers.utils.getRandomBytes is not a function. (In '_ethers.ethers.utils.getRandomBytes(32)', '_ethers.ethers.utils.getRandomBytes' is undefined) │ │ This error is located at: │ in App (at renderApplication.js:45) │ in RCTView (at View.js:34) │ in View (at AppContainer.js:106) │ in RCTView (at View.js:34) │ in View (at AppContainer.js:132) └ in AppContainer (at renderApplication.js:39)

Tried callback ethers.utils.getRandomBytes(32).then() and the error seems to be the same.

emclab commented 4 years ago

ethers.utils.randomBytes(32) return output (a is the returned):

[21:10:50] I | ReactNativeJS ▶︎ 'a : ', { '0': 0, │ '1': 0, │ '2': 0, │ '3': 0, │ '4': 0, │ '5': 0, │ '6': 0, │ '7': 0, │ '8': 0, │ '9': 0, │ '10': 0, │ '11': 0, │ '12': 0, │ '13': 0, │ '14': 0, │ '15': 0, │ '16': 0, │ '17': 0, │ '18': 0, │ '19': 0, │ '20': 0, │ '21': 0, │ '22': 0, │ '23': 0, │ '24': 0, │ '25': 0, │ '26': 0, │ '27': 0, │ '28': 0, │ '29': 0, │ '30': 0, │ '31': 0 } └ Remove this later; just checking to make sure this is used

ricmoo commented 4 years ago

So, yes, it isn't working... The library returns a Promise, so it cannot be used this way (synchronously)...

I do recall trying that library now, and encountering the same problem... :(

emclab commented 4 years ago

added await and still output 0s:

     const random = await generateSecureRandom(array.length);

But this requires the async call out of your lib I guess. It seems to be really big issue to get a secure random string!

ricmoo commented 4 years ago

Yeah... :(

emclab commented 4 years ago

Shall I use react-native-crypto instead? Or whenever the app require a random string, the app calls the backend server and requires one. I am thinking to require a longer length random string from server and "randomly" pick out of it on app and form a string to its designated length to avoid being sniffed over the internet transportation. But I am not sure if the string generated will be random enough.

ricmoo commented 4 years ago

Never call a server for random numbers. That is a significant security vulnerability! Never ever. Ever! :p

Choosing a longer string and all that jazz does not improve the quality of the random string and puts a crazy serious attack vector in your app.

Also, that would be async anyways, at which point you might as well use the securerandom library.

The react-native-crypto is your best bet (least worst option), and while I got it working their build instructions were a bit convoluted.

emclab commented 4 years ago

react-native-crypto!

But I don't quite understand why requiring a random string base from server would be a significant security risk. The frontend app only uses the random string base returned ( over https certainly) to form the random string needed locally. Let's say someone get hold of the random string returned by the server, he/she only knows that the random string on app will be created out of the string he/she has but still does not know exactly what the random string the app is going to have. On the other hand the sniffer only knows the char set to be used to form random string on app, just like we know any random string will be out of char set of 10digit + 26 char (up/low case).

ricmoo commented 4 years ago

But that random string will be used in some deterministic fashion by the app, which is logic an attacker that sniffed that data can also replay. Keep in mind even having a little bit of knowledge greatly impacts the security profile. There is no way to add entropy without a truly random source, so if you send that string to the device, entropy can only be lost, reducing the security.

"but still does not know exactly what the random string the app is going to have"

i.e. If they get that string, they will have a VERY good idea of what the string will be be, even if you stir it up a bit, they can stir it in similar ways.

There are other issues as well to keep in mind:

a) I, the app user, must trust the entropy coming from your server. If you decide to be malicious or if someone hijacks (hacks into your server) they can select the data feed me

b) Many school networks use authenticated certificates for their WiFi and coffee shops MitM traffic for authentication purposes; in either of these cases it may be possible for the in-flight entropy to be discovered. On many OSes I may get a simple dialog box indicating "Do you wish to trust this entity: Starbucks", in which case selecting "OK" (which most users just tap aimlessly) now renders all my content viewable by middle parties.

c) Replay; even if the data is encrypted, it could have been logged somewhere, which means some day the decryption keys may come to light, exposing historic data used

HTTPS is a must but is not a magic bullet, and why not let the device use a true random source, which is has available locally.

The big advantage of blockchain is we have a truly trestles system. There is no need to add the need to trust other parties. If you use a compromised source for entropy to generate private keys, they will be stolen from. This has happened many times before (e.g. brain wallets, faulty use of JavaScript RNG and there was a case where someone was querying on-line sources for random numbers) and all ended up stolen; luckily in a few cases, by white hats.

Long story short, do not use random data from a server. :)

emclab commented 4 years ago

Great comments. Security is always a big concern but a lot of time a user ( me for ex) just can not think of all the possible consequences an action may comes with.

emclab commented 4 years ago

It seems to me that there is no truly random ever existing. Any random is deterministic algorithmatically. This may be the cause of related security issue.

ricmoo commented 4 years ago

Oh yes. Security is quite hard to get right, which is why there is such a (justified) stigma against “rolling your own crypto”. :)

There are true random sources. Your phone uses low bits in the noise in the EM spectrum on their BlueTooth, Wifi and GSM antennae, which is basically chunks of static left over from the Big Bang, so pretty hard to guess. :)

Computers will use similar sources, as well as stirring in network traffic, keystroke timestamps, hard drive jitter and whatnot. Even this stirring though is very crucial to be done in ways that do not damage the underlying entropy they are trying to mix.

But these are things hardware designers and OS architects figure out for us, which is why it’s best to use what they give us. :)

emclab commented 4 years ago

Is there way I can test the installation of react-native-crypto? The installation went without error.

emclab commented 4 years ago

After removing react-native-securerandom from the app, the same test code

  let a = await ethers.utils.randomBytes(32);
  console.log("a : ", a); 

generated the following random output:

   [Wed Oct 07 2020 21:34:55.711]  WARN     Setting a timer for a long period of time, i.e. multiple minutes, is a performance and correctness issue on Android as it keeps the timer module awake, and timers can only be called when the app is in the foreground. See https://github.com/facebook/react-native/issues/12981 for more info.
 (Saw setTimeout with duration 120000ms)
   [Wed Oct 07 2020 21:34:55.713]  LOG      a :  [203, 209, 49, 68, 230, 96, 32, 87, 254, 210, 13, 132, 134, 53, 46, 17, 200, 226, 74, 84, 214, 121, 226, 12, 173, 246, 242, 52, 77, 155, 137, 68]

Is it random enough to be used with ethers.js? react-native-crypto was not installed. Not sure what is the warning about the timer.

emclab commented 4 years ago

Came across [react-native-get-random-values](https://github.com/LinusU/react-native-get-random-values) claiming strong random in RN for module calling crypto.getRandomValues. The module is actively maintained and has currently ~140K weekly download. Also it does not return promise and is sync op.

ricmoo commented 4 years ago

Awesome! That looks perfect!! I checked the source and it looks good.

I’ll update the docs with package.

Thanks! :)

emclab commented 4 years ago

Sounds great! No shim needed, isn't it?

react-native-crypto is pain to use with rn-nodeify which introduces modification and quite a few modules. Some of modules may not be used at all and may also be out of date.

emclab commented 4 years ago

Just installed react-native-get-random-values and import it at top of App.js. Here is the console output:

   [Thu Oct 08 2020 12:27:12.456]  LOG      a :  [155, 34, 116, 184, 69, 174, 29, 185, 193, 169, 81, 237, 231, 76, 132, 96, 202, 21, 198, 216, 96, 182, 130, 69, 9, 27, 3, 141, 151, 56, 91, 162]
manoj398 commented 4 years ago

crypto.randomBytes shim is not working in RN.

image

Lloyd1229 commented 4 years ago

how get erc20 balanceOf

    const  mainProvider = new ethers.providers.JsonRpcProvider(url);
    let  content= new ethers.Contract(erc20_token,erc20_abi,mainProvider);
   content.balanceOf(address).then(res=>{
       console.log(res);
  })

image

zemse commented 4 years ago

@Lloyd1229 Can you double check if the erc20 token address and provider is correct? If contract does not exist at that address then, you'd get such error.

nikita-zot-1408 commented 3 years ago

I tested solution given on this website https://docs.ethers.io/v5/cookbook/react-native/ but I am still getting this error when trying to get the token balance Error: could not detect network (event="noNetwork", code=NETWORK_ERROR, version=providers/5.0.23). Anyone knows solution to this please?

BryanOx commented 3 years ago

I am getting the error warning of no secure random source available with expo sdk 42.0.1 even with react-native-get-random-values imported at the top of my App.js The error: [Unhandled promise rejection: Error: no secure random source avaialble (operation="crypto.getRandomValues", code=UNSUPPORTED_OPERATION, version=random/5.4.0)]

loekTheDreamer commented 3 years ago

@nikita-zot-1408 the reason why your getting an error is that ios only access HTTPS. so use ngrok to create a tunnel to your localhost then you will be able to get the balance.

ricmoo commented 3 years ago

@loekTheDreamer Any change I could bug you for a quick walk-through or a link that I could add to the cookbook? :)

loekTheDreamer commented 3 years ago

@loekTheDreamer Any change I could bug you for a quick walk-through or a link that I could add to the cookbook? :)

sure, just not sure what you mean? @ricmoo

ricmoo commented 3 years ago

I’ve never set up ngrok, if you have any info on setting it up for iOS, I’d include it in the cookbook in the React Native section. :)

loekTheDreamer commented 3 years ago

Oh, it's super simple @ricmoo just follow these steps:

  1. Go create an account at https://ngrok.com/
  2. Login to your dashboard
  3. On Linux or Mac OS X you can unzip ngrok from a terminal with the following command. On Windows, just double click ngrok.zip to extract it. unzip /path/to/ngrok.zip
  4. Running this command will add your authtoken to the default ngrok.yml configuration file. This will grant you access to more features and longer session times. Running tunnels will be listed on the status page of the dashboard. ./ngrok authtoken yourSecretToken
  5. then run the following command where ngrok is located using your selected port. ./ngrok http 7545 Screenshot 2021-10-05 at 19 46 30
  6. then just use the https URL and presto