From 1ced8990b9e21fa17c788d93a151ec1164ebbce5 Mon Sep 17 00:00:00 2001 From: Yeastplume Date: Tue, 18 Feb 2020 12:28:23 +0000 Subject: [PATCH] Add sample script to init and call the secure owner API from node.js (#335) * add sample script to init and call the secure owner API from node * fix xiaojay's name.. thanks xiaojay! --- doc/samples/v3_api_node/package-lock.json | 115 +++++++++++++++++++ doc/samples/v3_api_node/package.json | 14 +++ doc/samples/v3_api_node/readme.md | 28 +++++ doc/samples/v3_api_node/src/index.js | 134 ++++++++++++++++++++++ 4 files changed, 291 insertions(+) create mode 100644 doc/samples/v3_api_node/package-lock.json create mode 100644 doc/samples/v3_api_node/package.json create mode 100644 doc/samples/v3_api_node/readme.md create mode 100644 doc/samples/v3_api_node/src/index.js diff --git a/doc/samples/v3_api_node/package-lock.json b/doc/samples/v3_api_node/package-lock.json new file mode 100644 index 00000000..16c044e4 --- /dev/null +++ b/doc/samples/v3_api_node/package-lock.json @@ -0,0 +1,115 @@ +{ + "name": "node-sample", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/connect": { + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", + "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", + "requires": { + "@types/node": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz", + "integrity": "sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==", + "requires": { + "@types/node": "*", + "@types/range-parser": "*" + } + }, + "@types/lodash": { + "version": "4.14.149", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", + "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==" + }, + "@types/node": { + "version": "12.12.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.27.tgz", + "integrity": "sha512-odQFl/+B9idbdS0e8IxDl2ia/LP8KZLXhV3BUeI98TrZp0uoIzQPhGd+5EtzHmT0SMOIaPd7jfz6pOHLWTtl7A==" + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "^4.0.3" + } + }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" + }, + "jayson": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jayson/-/jayson-3.2.0.tgz", + "integrity": "sha512-DZQnwA57GcStw4soSYB2VntWXFfoWvmSarlaWePDYOWhjxT72PBM4atEBomaTaS1uqk3jFC9UO9AyWjlujo3xw==", + "requires": { + "@types/connect": "^3.4.32", + "@types/express-serve-static-core": "^4.16.9", + "@types/lodash": "^4.14.139", + "@types/node": "^12.7.7", + "JSONStream": "^1.3.1", + "commander": "^2.12.2", + "es6-promisify": "^5.0.0", + "eyes": "^0.1.8", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "uuid": "^3.2.1" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } + } +} diff --git a/doc/samples/v3_api_node/package.json b/doc/samples/v3_api_node/package.json new file mode 100644 index 00000000..1126955d --- /dev/null +++ b/doc/samples/v3_api_node/package.json @@ -0,0 +1,14 @@ +{ + "name": "node-sample", + "version": "0.0.1", + "description": "Sample of connecting to the secure OwnerAPI via node", + "main": "src/index.js", + "scripts": { + "test": "npm test" + }, + "author": "", + "license": "ISC", + "dependencies": { + "jayson": "^3.2.0" + } +} diff --git a/doc/samples/v3_api_node/readme.md b/doc/samples/v3_api_node/readme.md new file mode 100644 index 00000000..e08ccbfc --- /dev/null +++ b/doc/samples/v3_api_node/readme.md @@ -0,0 +1,28 @@ +# Connecting to the wallet's V3 Owner API from Node + +This is a small sample with code that demonstrates how to initialize the Wallet V3's Secure API and call API functions through it. + +To run this sample: + +First run the Owner API: + +```.sh +grin-wallet owner_api +``` + +This sample doesn't use the authentication specified in the wallet's `.api_secret`, so before running the owner_api please ensure api authentication is commented out in `grin-wallet.toml`. Including the authentication token as part of the request is a function of your json-rpc client library of choice, so it's not included in the sample to make setup a bit simpler. + +ensure the client url in `src\index.js` is set correctly: + +```.sh +const client = jayson.client.http('http://localhost:3420/v3/owner'); +``` + +Then (assuming node.js and npm are installed on the system): + +```.sh +npm install +node src/index.json +``` + +Feel free to play around with the sample, modifying it to call whatever functions you'd like to see in operation! diff --git a/doc/samples/v3_api_node/src/index.js b/doc/samples/v3_api_node/src/index.js new file mode 100644 index 00000000..6de1c5be --- /dev/null +++ b/doc/samples/v3_api_node/src/index.js @@ -0,0 +1,134 @@ +/* Sample Code for connecting to the V3 Secure API via Node + * + * With thanks to xiaojay of Niffler Wallet: + * https://github.com/grinfans/Niffler/blob/gw3/src/shared/walletv3.js + * + */ + +const jayson = require('jayson/promise'); +const crypto = require('crypto'); + +const client = jayson.client.http('http://localhost:3420/v3/owner'); + +// Demo implementation of using `aes-256-gcm` with node.js's `crypto` lib. +const aes256gcm = (shared_secret) => { + const ALGO = 'aes-256-gcm'; + + // encrypt returns base64-encoded ciphertext + const encrypt = (str, nonce) => { + let key = Buffer.from(shared_secret, 'hex') + const cipher = crypto.createCipheriv(ALGO, key, nonce) + const enc = Buffer.concat([cipher.update(str, 'utf8'), cipher.final()]) + const tag = cipher.getAuthTag() + return Buffer.concat([enc, tag]).toString('base64') + }; + + // decrypt decodes base64-encoded ciphertext into a utf8-encoded string + const decrypt = (enc, nonce) => { + //key,nonce is all buffer type; data is base64-encoded string + let key = Buffer.from(shared_secret, 'hex') + const data_ = Buffer.from(enc, 'base64') + const decipher = crypto.createDecipheriv(ALGO, key, nonce) + const len = data_.length + const tag = data_.slice(len-16, len) + const text = data_.slice(0, len-16) + decipher.setAuthTag(tag) + const dec = decipher.update(text, 'binary', 'utf8') + decipher.final('utf8'); + return dec + }; + + return { + encrypt, + decrypt, + }; +}; + +class JSONRequestEncrypted { + constructor(id, method, params) { + this.jsonrpc = '2.0' + this.method = method + this.id = id + this.params = params + } + + async send(key){ + const aesCipher = aes256gcm(key); + const nonce = new Buffer.from(crypto.randomBytes(12)); + let enc = aesCipher.encrypt(JSON.stringify(this), nonce); + console.log("Encrypted: " + enc) + let params = { + 'nonce': nonce.toString('hex'), + 'body_enc': enc, + } + let response = await client.request('encrypted_request_v3', params); + + if (response.err) { + throw response.err + } + + const nonce2 = Buffer.from(response.result.Ok.nonce, 'hex'); + const data = Buffer.from(response.result.Ok.body_enc, 'base64'); + + let dec = aesCipher.decrypt(data, nonce2) + return dec + } +} + +async function initSecure() { + let ecdh = crypto.createECDH('secp256k1') + ecdh.generateKeys() + let publicKey = ecdh.getPublicKey('hex', 'compressed') + const params = { + 'ecdh_pubkey': publicKey + } + let response = await client.request('init_secure_api', params); + if (response.err) { + throw response.err + } + + return ecdh.computeSecret(response.result.Ok, 'hex', 'hex') +} + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function main() { + let shared_key = await initSecure(); + + let response = await new JSONRequestEncrypted(1, 'open_wallet', { + "name": null, + "password": "", + }).send(shared_key); + + let token = JSON.parse(response).result.Ok; + + let iterations = 1; + + for (i=0; i