├── docs ├── include_announcement.rst ├── include_blockchain_note.rst ├── getting-started.rst ├── Makefile ├── index.rst ├── make.bat ├── panaceajs.rst ├── panaceajs-tutorial.rst ├── panaceajs-identification.rst ├── conf.py ├── panaceajs-utils.rst ├── panaceajs-healthData.rst └── panaceajs-account.rst ├── .eslintignore ├── test ├── healthData │ ├── samples │ │ ├── pghd.txt │ │ ├── pghd.json │ │ ├── ultrasound_sample.dcm │ │ ├── observation_sample.json │ │ ├── patient_sample.json │ │ └── careplan_sample.json │ └── index.js ├── helpers │ └── chaiHexString.js ├── client │ ├── client.js │ ├── httpRequest.js │ ├── config.js │ ├── nodeBucket.js │ └── gateway.js ├── .eslintrc ├── local │ ├── transaction │ │ ├── utils │ │ │ ├── setTx.js │ │ │ ├── hashTx.js │ │ │ ├── validateTx.js │ │ │ ├── wrapTxCreator.js │ │ │ └── checkTx.js │ │ ├── tx_vest.js │ │ ├── tx_quitCandidacy.js │ │ ├── tx_becomeCandidate.js │ │ ├── tx_withdrawVesting.js │ │ ├── tx_vote.js │ │ ├── tx_dataUpload.js │ │ ├── tx_revokeCertification.js │ │ ├── tx_addCertification.js │ │ ├── payload.js │ │ └── tx_valueTransfer.js │ └── account │ │ └── account.js ├── utils │ ├── hash.js │ └── utils.js ├── cryptography │ ├── sign.js │ ├── keyGen.js │ └── encrypt.js └── index.js ├── src ├── client │ ├── index.js │ ├── apis │ │ ├── node.js │ │ ├── account.js │ │ ├── consensus.js │ │ ├── index.js │ │ ├── subscribe.js │ │ ├── block.js │ │ └── transaction.js │ ├── constants.js │ ├── parser.js │ ├── client.js │ ├── httpRequest.js │ ├── nodeBucket.js │ ├── config.js │ └── gateway.js ├── local │ ├── account │ │ ├── index.js │ │ └── account.js │ ├── index.js │ └── transaction │ │ ├── utils │ │ ├── proto │ │ │ ├── Makefile │ │ │ ├── transaction.proto │ │ │ └── transaction.pb.json │ │ ├── wrapTxCreator.js │ │ ├── index.js │ │ ├── validateTx.js │ │ ├── setTx.js │ │ ├── checkTx.js │ │ ├── hashTx.js │ │ └── constants.js │ │ ├── tx_vest.js │ │ ├── tx_vote.js │ │ ├── tx_dataUpload.js │ │ ├── tx_quitCandidacy.js │ │ ├── tx_valueTransfer.js │ │ ├── tx_becomeCandidate.js │ │ ├── tx_withdrawVesting.js │ │ ├── tx_addCertification.js │ │ ├── tx_revokeCertification.js │ │ ├── index.js │ │ └── payload.js ├── config.js ├── identification │ ├── index.js │ └── certificate.js ├── healthData │ ├── index.js │ ├── proto │ │ ├── stu3 │ │ │ ├── test.proto │ │ │ ├── primitive_has_no_value.proto │ │ │ ├── structuredefinition_regex.proto │ │ │ ├── elementdefinition_binding_name.proto │ │ │ ├── base64_separator_stride.proto │ │ │ ├── structuredefinition_explicit_type_name.proto │ │ │ └── annotations.proto │ │ └── stu3min │ │ │ ├── observation.proto │ │ │ ├── patient.proto │ │ │ ├── careplan.proto │ │ │ └── common.proto │ ├── hash.js │ ├── mhd.js │ ├── reader.js │ ├── constants.js │ ├── encoder.js │ └── helper.js ├── utils │ ├── index.js │ ├── utils.js │ ├── hash.js │ └── proto.js ├── cryptography │ ├── index.js │ ├── sign.js │ ├── keyGen.js │ └── encrypt.js └── index.js ├── .npmignore ├── .eslintrc ├── .gitignore ├── .babelrc ├── README.md ├── package.json └── LICENSE /docs/include_announcement.rst: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | docs/ 3 | lib/ -------------------------------------------------------------------------------- /test/healthData/samples/pghd.txt: -------------------------------------------------------------------------------- 1 | It is a patient generated health data. -------------------------------------------------------------------------------- /src/client/index.js: -------------------------------------------------------------------------------- 1 | import client from './client'; 2 | 3 | export default client; 4 | -------------------------------------------------------------------------------- /src/local/account/index.js: -------------------------------------------------------------------------------- 1 | import Account from './account'; 2 | 3 | export default { 4 | Account, 5 | }; 6 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | const ALG = 1; 2 | const CHAIN_ID = 181228; 3 | 4 | export default { 5 | ALG, 6 | CHAIN_ID, 7 | }; 8 | -------------------------------------------------------------------------------- /test/healthData/samples/pghd.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": "2018-05-24", 3 | "text": "It is a patient generated health data." 4 | } -------------------------------------------------------------------------------- /src/identification/index.js: -------------------------------------------------------------------------------- 1 | import certificate from './certificate'; 2 | 3 | export default { 4 | ...certificate, 5 | }; 6 | -------------------------------------------------------------------------------- /test/healthData/samples/ultrasound_sample.dcm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/medibloc/panacea-js-old/HEAD/test/healthData/samples/ultrasound_sample.dcm -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .babelrc 2 | .eslintignore 3 | .eslintrc.js 4 | .idea 5 | .gitignore 6 | 7 | .vscode/ 8 | docs/ 9 | example/ 10 | src/ 11 | test/ 12 | -------------------------------------------------------------------------------- /src/healthData/index.js: -------------------------------------------------------------------------------- 1 | import hash from './hash'; 2 | import encoder from './encoder'; 3 | 4 | export default { 5 | ...encoder, 6 | ...hash, 7 | }; 8 | -------------------------------------------------------------------------------- /src/local/index.js: -------------------------------------------------------------------------------- 1 | import { Account } from './account'; 2 | import transaction from './transaction'; 3 | 4 | export default { 5 | Account, 6 | transaction, 7 | }; 8 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import hash from './hash'; 2 | import proto from './proto'; 3 | import utils from './utils'; 4 | 5 | export default { 6 | ...hash, 7 | ...proto, 8 | ...utils, 9 | }; 10 | -------------------------------------------------------------------------------- /src/cryptography/index.js: -------------------------------------------------------------------------------- 1 | import encrypt from './encrypt'; 2 | import keyGen from './keyGen'; 3 | import sign from './sign'; 4 | 5 | export default { 6 | ...encrypt, 7 | ...keyGen, 8 | ...sign, 9 | }; 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb-base", 3 | "rules": { 4 | "no-console": "off", 5 | }, 6 | "settings": { 7 | "import/resolver": { 8 | "babel-module": {} 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/healthData/proto/stu3/test.proto: -------------------------------------------------------------------------------- 1 | // test.proto 2 | syntax = "proto3"; 3 | 4 | package testpackage; 5 | 6 | message CarePlan { 7 | string test_name = 1; // becomes testName 8 | string test_content = 2; // becomes testContent 9 | } -------------------------------------------------------------------------------- /src/client/apis/node.js: -------------------------------------------------------------------------------- 1 | export default ({ sendRequest }) => { 2 | const getMedState = () => sendRequest({ 3 | method: 'get', 4 | path: 'v1/node/medstate', 5 | }); 6 | 7 | return { 8 | getMedState, 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /src/local/transaction/utils/proto/Makefile: -------------------------------------------------------------------------------- 1 | PB = $(wildcard *.proto) 2 | JSON = $(PB:.proto=.pb.json) 3 | 4 | all: $(JSON) 5 | 6 | %.pb.json: %.proto 7 | ../../../../../node_modules/.bin/pbjs -t json $< > $@ 8 | 9 | %.proto: 10 | 11 | clean: 12 | rm *.pb.json 13 | -------------------------------------------------------------------------------- /docs/include_blockchain_note.rst: -------------------------------------------------------------------------------- 1 | .. note:: 2 | You can test the library by running the MediBloc blockchain on a local machine as Testnet or Mainnet are not yet launched. 3 | Please refer to `go-medibloc `_ to run MediBloc blockchain on a local machine. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #General 2 | .DS_Store 3 | 4 | # Distribution folders 5 | dist/*.js 6 | dist/*.json 7 | lib 8 | 9 | # Dependency directories 10 | node_modules 11 | 12 | #IDE directories 13 | .idea 14 | .vscode 15 | 16 | ##Temporary ignore for documentation 17 | docs/_build 18 | 19 | #Logs 20 | npm-debug.log* -------------------------------------------------------------------------------- /src/client/constants.js: -------------------------------------------------------------------------------- 1 | export const APPLICATION_JSON = { 'content-type': 'application/json' }; 2 | 3 | export const MAX_REQUEST_RETRY_COUNT = 10; 4 | 5 | export const STREAM_TIMEOUT = 300000; 6 | export const NETWORK_TIMEOUT = 5000; 7 | 8 | export const GET = 'get'; 9 | export const POST = 'post'; 10 | -------------------------------------------------------------------------------- /src/local/transaction/utils/wrapTxCreator.js: -------------------------------------------------------------------------------- 1 | import hashTx from './hashTx'; 2 | 3 | const wrapTxCreator = creator => (fields) => { 4 | const rawTx = creator(fields); 5 | return { 6 | hash: hashTx(rawTx), 7 | rawTx, 8 | sign: null, 9 | }; 10 | }; 11 | 12 | export default wrapTxCreator; 13 | -------------------------------------------------------------------------------- /src/client/apis/account.js: -------------------------------------------------------------------------------- 1 | export default ({ sendRequest }) => { 2 | const getAccount = (address, height, type) => sendRequest({ 3 | method: 'get', 4 | path: 'v1/account', 5 | payload: { 6 | address, height, type, 7 | }, 8 | }); 9 | 10 | return { 11 | getAccount, 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /src/client/parser.js: -------------------------------------------------------------------------------- 1 | export const parseTransaction = tx => ({ 2 | ...tx, 3 | timestamp: ((tx || {}).receipt || {}).timestamp, 4 | }); 5 | 6 | export const parseTransactions = transactions => 7 | transactions.map(tx => ({ 8 | ...tx, 9 | timestamp: ((tx || {}).receipt || {}).timestamp, 10 | })); 11 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015" 4 | ], 5 | "plugins": [ 6 | "add-module-exports", 7 | "babel-plugin-transform-object-rest-spread", 8 | "transform-runtime", 9 | ["module-resolver", { 10 | "root": ["./src"], 11 | "alias": { 12 | "test": ["./test"] 13 | } 14 | } 15 | ] 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/client/apis/consensus.js: -------------------------------------------------------------------------------- 1 | export default ({ sendRequest }) => { 2 | const getCandidates = () => sendRequest({ 3 | method: 'get', 4 | path: 'v1/candidates', 5 | }); 6 | 7 | const getDynasty = () => sendRequest({ 8 | method: 'get', 9 | path: 'v1/dynasty', 10 | }); 11 | 12 | return { 13 | getCandidates, 14 | getDynasty, 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /src/client/apis/index.js: -------------------------------------------------------------------------------- 1 | import account from './account'; 2 | import block from './block'; 3 | import consensus from './consensus'; 4 | import node from './node'; 5 | import subscribe from './subscribe'; 6 | import transaction from './transaction'; 7 | 8 | 9 | export default { 10 | account, 11 | block, 12 | consensus, 13 | node, 14 | subscribe, 15 | transaction, 16 | }; 17 | -------------------------------------------------------------------------------- /src/local/transaction/utils/index.js: -------------------------------------------------------------------------------- 1 | import checkTx from './checkTx'; 2 | import constants from './constants'; 3 | import hashTx from './hashTx'; 4 | import setTx from './setTx'; 5 | import validateTx from './validateTx'; 6 | import wrapTxCreator from './wrapTxCreator'; 7 | 8 | export default { 9 | checkTx, 10 | constants, 11 | hashTx, 12 | setTx, 13 | validateTx, 14 | wrapTxCreator, 15 | }; 16 | -------------------------------------------------------------------------------- /src/local/transaction/tx_vest.js: -------------------------------------------------------------------------------- 1 | import { constants, setTx, validateTx, wrapTxCreator } from './utils'; 2 | 3 | const { 4 | REQUIRED_TX_PARAMS, 5 | VEST, 6 | } = constants; 7 | 8 | const createTx = (fields) => { 9 | const tx = setTx(Object.assign({}, fields, { type: VEST })); 10 | validateTx(tx, REQUIRED_TX_PARAMS[VEST]); 11 | return tx; 12 | }; 13 | 14 | export default wrapTxCreator(createTx); 15 | -------------------------------------------------------------------------------- /src/local/transaction/tx_vote.js: -------------------------------------------------------------------------------- 1 | import { constants, setTx, validateTx, wrapTxCreator } from './utils'; 2 | 3 | const { 4 | REQUIRED_TX_PARAMS, 5 | VOTE, 6 | } = constants; 7 | 8 | const createTx = (fields) => { 9 | const tx = setTx(Object.assign({}, fields, { type: VOTE })); 10 | validateTx(tx, REQUIRED_TX_PARAMS[VOTE]); 11 | return tx; 12 | }; 13 | 14 | export default wrapTxCreator(createTx); 15 | -------------------------------------------------------------------------------- /src/local/transaction/tx_dataUpload.js: -------------------------------------------------------------------------------- 1 | import { constants, setTx, validateTx, wrapTxCreator } from './utils'; 2 | 3 | const { 4 | DATA_UPLOAD, 5 | REQUIRED_TX_PARAMS, 6 | } = constants; 7 | 8 | const createTx = (fields) => { 9 | const tx = setTx(Object.assign({}, fields, { type: DATA_UPLOAD })); 10 | validateTx(tx, REQUIRED_TX_PARAMS[DATA_UPLOAD]); 11 | return tx; 12 | }; 13 | 14 | export default wrapTxCreator(createTx); 15 | -------------------------------------------------------------------------------- /src/local/transaction/utils/validateTx.js: -------------------------------------------------------------------------------- 1 | import { 2 | checkObject, 3 | checkRequiredParams, 4 | checkValue, 5 | } from './checkTx'; 6 | 7 | const validateTx = (tx, requiredParams = []) => { 8 | checkObject(tx); 9 | checkRequiredParams(tx, requiredParams); 10 | requiredParams.forEach((param) => { 11 | if (param === 'value') { 12 | checkValue(tx); 13 | } 14 | }); 15 | }; 16 | 17 | export default validateTx; 18 | -------------------------------------------------------------------------------- /src/client/apis/subscribe.js: -------------------------------------------------------------------------------- 1 | export default ({ sendRequest }) => { 2 | const subscribe = (topics) => { 3 | if (!Array.isArray(topics)) { 4 | throw new Error('topics should be an array type'); 5 | } 6 | return sendRequest({ 7 | method: 'post', 8 | path: 'v1/subscribe', 9 | payload: { 10 | topics, 11 | }, 12 | }, true); 13 | }; 14 | 15 | return { 16 | subscribe, 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /src/local/transaction/tx_quitCandidacy.js: -------------------------------------------------------------------------------- 1 | import { constants, setTx, validateTx, wrapTxCreator } from './utils'; 2 | 3 | const { 4 | REQUIRED_TX_PARAMS, 5 | QUIT_CANDIDATE, 6 | } = constants; 7 | 8 | const createTx = (fields) => { 9 | const tx = setTx(Object.assign({}, fields, { type: QUIT_CANDIDATE })); 10 | validateTx(tx, REQUIRED_TX_PARAMS[QUIT_CANDIDATE]); 11 | return tx; 12 | }; 13 | 14 | export default wrapTxCreator(createTx); 15 | -------------------------------------------------------------------------------- /src/local/transaction/tx_valueTransfer.js: -------------------------------------------------------------------------------- 1 | import { constants, setTx, validateTx, wrapTxCreator } from './utils'; 2 | 3 | const { 4 | REQUIRED_TX_PARAMS, 5 | VALUE_TRANSFER, 6 | } = constants; 7 | 8 | const createTx = (fields) => { 9 | const tx = setTx(Object.assign({}, fields, { type: VALUE_TRANSFER })); 10 | validateTx(tx, REQUIRED_TX_PARAMS[VALUE_TRANSFER]); 11 | return tx; 12 | }; 13 | 14 | export default wrapTxCreator(createTx); 15 | -------------------------------------------------------------------------------- /src/local/transaction/tx_becomeCandidate.js: -------------------------------------------------------------------------------- 1 | import { constants, setTx, validateTx, wrapTxCreator } from './utils'; 2 | 3 | const { 4 | REQUIRED_TX_PARAMS, 5 | BECOME_CANDIDATE, 6 | } = constants; 7 | 8 | const createTx = (fields) => { 9 | const tx = setTx(Object.assign({}, fields, { type: BECOME_CANDIDATE })); 10 | validateTx(tx, REQUIRED_TX_PARAMS[BECOME_CANDIDATE]); 11 | return tx; 12 | }; 13 | 14 | export default wrapTxCreator(createTx); 15 | -------------------------------------------------------------------------------- /src/local/transaction/tx_withdrawVesting.js: -------------------------------------------------------------------------------- 1 | import { constants, setTx, validateTx, wrapTxCreator } from './utils'; 2 | 3 | const { 4 | REQUIRED_TX_PARAMS, 5 | WITHDRAW_VESTING, 6 | } = constants; 7 | 8 | const createTx = (fields) => { 9 | const tx = setTx(Object.assign({}, fields, { type: WITHDRAW_VESTING })); 10 | validateTx(tx, REQUIRED_TX_PARAMS[WITHDRAW_VESTING]); 11 | return tx; 12 | }; 13 | 14 | export default wrapTxCreator(createTx); 15 | -------------------------------------------------------------------------------- /src/local/transaction/tx_addCertification.js: -------------------------------------------------------------------------------- 1 | import { constants, setTx, validateTx, wrapTxCreator } from './utils'; 2 | 3 | const { 4 | REQUIRED_TX_PARAMS, 5 | ADD_CERTIFICATION, 6 | } = constants; 7 | 8 | const createTx = (fields) => { 9 | const tx = setTx(Object.assign({}, fields, { type: ADD_CERTIFICATION })); 10 | validateTx(tx, REQUIRED_TX_PARAMS[ADD_CERTIFICATION]); 11 | return tx; 12 | }; 13 | 14 | export default wrapTxCreator(createTx); 15 | -------------------------------------------------------------------------------- /test/helpers/chaiHexString.js: -------------------------------------------------------------------------------- 1 | export default (chai) => { 2 | chai.Assertion.addProperty('hexString', function handleAssert() { 3 | const { _obj: obj } = this; 4 | new chai.Assertion(obj).to.be.a('string'); 5 | const expected = Buffer.from(obj, 'hex').toString('hex'); 6 | this.assert( 7 | expected === obj, 8 | 'expected #{this} to be a hexString', 9 | 'expected #{this} not to be a hexString', 10 | ); 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /src/local/transaction/tx_revokeCertification.js: -------------------------------------------------------------------------------- 1 | import { constants, setTx, validateTx, wrapTxCreator } from './utils'; 2 | 3 | const { 4 | REQUIRED_TX_PARAMS, 5 | REVOKE_CERTIFICATION, 6 | } = constants; 7 | 8 | const createTx = (fields) => { 9 | const tx = setTx(Object.assign({}, fields, { type: REVOKE_CERTIFICATION })); 10 | validateTx(tx, REQUIRED_TX_PARAMS[REVOKE_CERTIFICATION]); 11 | return tx; 12 | }; 13 | 14 | export default wrapTxCreator(createTx); 15 | -------------------------------------------------------------------------------- /src/client/apis/block.js: -------------------------------------------------------------------------------- 1 | export default ({ sendRequest }) => { 2 | const getBlock = (hash, height, type) => sendRequest({ 3 | method: 'get', 4 | path: 'v1/block', 5 | payload: { 6 | hash, height, type, 7 | }, 8 | }); 9 | 10 | const getBlocks = (from, to) => sendRequest({ 11 | method: 'get', 12 | path: 'v1/blocks', 13 | payload: { 14 | from, to, 15 | }, 16 | }); 17 | 18 | return { 19 | getBlock, 20 | getBlocks, 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /test/client/client.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import client from 'client/client'; 3 | 4 | describe('client', () => { 5 | const defaultNodes = ['http://localhost:10000']; 6 | 7 | describe('#returned object', () => { 8 | let newClient; 9 | beforeEach(() => { 10 | newClient = client(defaultNodes); 11 | return Promise.resolve(); 12 | }); 13 | 14 | it('should be a object', () => { 15 | return expect(newClient) 16 | .to.be.an('object'); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/client/client.js: -------------------------------------------------------------------------------- 1 | import { 2 | account, 3 | block, 4 | consensus, 5 | node, 6 | subscribe, 7 | transaction, 8 | } from './apis'; 9 | import nodeBucket from './nodeBucket'; 10 | import gateway from './gateway'; 11 | 12 | export default (nodes) => { 13 | const apiGateway = gateway(nodeBucket(nodes)); 14 | return { 15 | ...account(apiGateway), 16 | ...block(apiGateway), 17 | ...consensus(apiGateway), 18 | ...node(apiGateway), 19 | ...subscribe(apiGateway), 20 | ...transaction(apiGateway), 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "globals": { 6 | "sandbox": true, 7 | "expect": true, 8 | "sinon": true 9 | }, 10 | "rules": { 11 | "arrow-body-style": "off", 12 | "no-unused-expressions": "off", 13 | "import/no-extraneous-dependencies": ["error", { "devDependencies": true }], 14 | }, 15 | "settings": { 16 | "import/resolver": { 17 | "node": { 18 | "extensions": [".js"], 19 | "moduleDirectory": [ 20 | "node_modules", 21 | "src" 22 | ] 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /docs/getting-started.rst: -------------------------------------------------------------------------------- 1 | .. _getting-started: 2 | 3 | .. include:: include_announcement.rst 4 | 5 | =============== 6 | Getting Started 7 | =============== 8 | 9 | `panacea-js `_ is a client-side JavaScript library for `medibloc blockchain `_. 10 | 11 | --------------------------------------------------------------------------- 12 | 13 | Adding panacea-js 14 | ------------ 15 | 16 | **npm** 17 | 18 | .. code-block:: javascript 19 | 20 | npm install @medibloc/panacea-js 21 | -------------------------------------------------------------------------------- /test/local/transaction/utils/setTx.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { Account } from 'local/account'; 3 | import setTx from 'local/transaction/utils/setTx'; 4 | 5 | // setTx 6 | describe('# signHashedTx function', () => { 7 | const user = new Account(''); 8 | const requiredNotNullParams = ['from', 'nonce', 'chain_id', 'alg']; 9 | 10 | it('Tx should have not null default value', () => { 11 | const tx = setTx({ from: user.pubKey }); 12 | Object.keys(tx).forEach(key => requiredNotNullParams.indexOf(key) !== -1 && expect(tx[key]).not.to.be.a('null')); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/local/transaction/utils/hashTx.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { Account } from 'local/account'; 3 | import { hashTx, setTx } from 'local/transaction/utils'; 4 | 5 | // hashTx 6 | describe('# hashTx function', () => { 7 | const user = new Account(''); 8 | const valueTransferTxData = { 9 | from: user.pubKey, 10 | to: user.pubKey, 11 | value: '5', 12 | nonce: 3, 13 | type: 'transfer', 14 | }; 15 | const valueTrasnferTx = setTx(valueTransferTxData); 16 | 17 | it('Should generate hex string', () => { 18 | expect(hashTx(valueTrasnferTx)).to.be.hexString; 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Client from './client'; 2 | import Cryptography from './cryptography'; 3 | import HealthData from './healthData'; 4 | import Identification from './identification'; 5 | import Local from './local'; 6 | import Utils from './utils'; 7 | 8 | export default { 9 | init: nodes => ({ 10 | client: Client(nodes), 11 | cryptography: Cryptography, 12 | healthData: HealthData, 13 | identification: Identification, 14 | local: Local, 15 | utils: Utils, 16 | }), 17 | client: nodes => Client(nodes), 18 | cryptography: Cryptography, 19 | healthData: HealthData, 20 | identification: Identification, 21 | local: Local, 22 | utils: Utils, 23 | }; 24 | -------------------------------------------------------------------------------- /src/client/httpRequest.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import http from 'http'; 3 | import https from 'https'; 4 | 5 | 6 | const connectedAxios = axios.create({ 7 | httpAgent: new http.Agent({ keepAlive: true }), 8 | httpsAgent: new https.Agent({ keepAlive: true }), 9 | }); 10 | 11 | // request sends HTTP request. 12 | const request = config => connectedAxios.request(config) 13 | .then(res => res.data) 14 | .catch((err) => { throw err.response; }); 15 | 16 | // asyncRequest sends HTTP request and hands over the response to callback. 17 | const asyncRequest = (config, cb) => request(config).then(res => cb(null, res)).catch(cb); 18 | 19 | export default { request, asyncRequest }; 20 | -------------------------------------------------------------------------------- /src/client/nodeBucket.js: -------------------------------------------------------------------------------- 1 | export default (nodes) => { 2 | if (!nodes || !Array.isArray(nodes) || nodes.length === 0) { 3 | throw new Error('nodeBucket requires array of nodes for initialization.'); 4 | } 5 | 6 | const size = nodes.length; 7 | let requestNodeIndex = 0; 8 | 9 | const getNodes = () => nodes; 10 | const getSize = () => size; 11 | const getRequestNode = () => nodes[requestNodeIndex]; 12 | 13 | // replaceRequestNode discards the requestNode and fill it. 14 | const replaceRequestNode = () => { 15 | requestNodeIndex = (requestNodeIndex + 1) % size; 16 | }; 17 | 18 | return { 19 | getNodes, 20 | getSize, 21 | getRequestNode, 22 | replaceRequestNode, 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /src/client/config.js: -------------------------------------------------------------------------------- 1 | import { 2 | APPLICATION_JSON, 3 | NETWORK_TIMEOUT, 4 | GET, 5 | POST, 6 | STREAM_TIMEOUT, 7 | } from './constants'; 8 | 9 | const buildConfig = ({ 10 | baseURL, 11 | method, 12 | path, 13 | payload, 14 | responseType = 'json', 15 | }) => { 16 | const customConfig = { 17 | baseURL, 18 | method, 19 | responseType, 20 | timeout: responseType === 'stream' ? STREAM_TIMEOUT : NETWORK_TIMEOUT, 21 | url: path, 22 | ...method === GET && { params: payload }, 23 | ...method === POST && { 24 | data: payload, 25 | headers: APPLICATION_JSON, 26 | }, 27 | }; 28 | 29 | return customConfig; 30 | }; 31 | 32 | export default { 33 | buildConfig, 34 | }; 35 | -------------------------------------------------------------------------------- /src/utils/utils.js: -------------------------------------------------------------------------------- 1 | import { randomBytes } from 'crypto'; 2 | 3 | const isAddress = (pubKey) => { 4 | try { 5 | return (Buffer.from(pubKey, 'hex').length === 33); 6 | } catch (err) { 7 | return false; 8 | } 9 | }; 10 | 11 | const isHexadecimal = str => /^[0-9a-fA-F]+$/.test(str); 12 | 13 | const padLeftWithZero = (str, len) => new Array((len > str.length) ? (len - str.length) + 1 : 0).join('0') + str; 14 | 15 | const genHexBuf = (str, bytesLen) => Buffer.from(padLeftWithZero(str, bytesLen * 2), 'hex'); 16 | 17 | const randomHex = (length = 16) => randomBytes(length).toString('hex'); 18 | 19 | export default { 20 | genHexBuf, 21 | isAddress, 22 | isHexadecimal, 23 | padLeftWithZero, 24 | randomHex, 25 | }; 26 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = panaceajs 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | 22 | .PHONY: clean 23 | # 24 | clean: 25 | rm -rf $(BUILDDIR)/* 26 | -------------------------------------------------------------------------------- /src/healthData/proto/stu3min/observation.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package buffer; 4 | 5 | import "common.proto"; 6 | 7 | message Observation { 8 | string status = 1; 9 | repeated Category category = 2; 10 | Code code = 3; 11 | string resourceType = 4; 12 | repeated Component component = 5; 13 | Meta meta = 6; 14 | string effectiveDateTime = 7; 15 | string id = 8; 16 | Context context = 9; 17 | Subject subject = 10; 18 | string issued = 11; 19 | ValueQuantity valueQuantity = 12; 20 | } 21 | 22 | message Component { 23 | ValueQuantity valueQuantity = 1; 24 | Code code = 2; 25 | } 26 | 27 | message ValueQuantity { 28 | string code = 1; 29 | string unit = 2; 30 | int32 value = 3; 31 | string system = 4; 32 | } -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: include_announcement.rst 2 | 3 | ======================================= 4 | panacea-js - MediBloc JavaScript API(v1.0.1) 5 | ======================================= 6 | 7 | `panacea-js `_ is a client-side JavaScript library for `medibloc blockchain `_. 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: User Documentation 12 | 13 | getting-started 14 | panaceajs-tutorial 15 | 16 | .. toctree:: 17 | :maxdepth: 2 18 | :caption: API Reference 19 | 20 | panaceajs 21 | panaceajs-client 22 | panaceajs-cryptography 23 | panaceajs-account 24 | panaceajs-transaction 25 | panaceajs-healthData 26 | panaceajs-identification 27 | panaceajs-utils 28 | -------------------------------------------------------------------------------- /src/local/transaction/utils/proto/transaction.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package corepb; 3 | 4 | message TransactionHashTarget { 5 | string tx_type = 1; 6 | bytes from = 2; 7 | bytes to = 3; 8 | bytes value = 4; 9 | uint64 nonce = 5; 10 | uint32 chain_id = 6; 11 | 12 | bytes payload = 10; 13 | } 14 | 15 | message TransactionPayerSignTarget { 16 | bytes hash = 1; 17 | bytes sign = 2; 18 | } 19 | 20 | message DefaultPayload { 21 | string message = 1; 22 | } 23 | 24 | message VotePayload { 25 | repeated bytes candidates = 1; 26 | } 27 | 28 | message AddCertificationPayload { 29 | int64 issue_time = 1; 30 | int64 expiration_time = 2; 31 | bytes hash = 3; 32 | } 33 | 34 | message RevokeCertificationPayload { 35 | bytes hash = 1; 36 | } 37 | 38 | message AddRecordPayload { 39 | bytes hash = 1; 40 | } 41 | -------------------------------------------------------------------------------- /src/healthData/proto/stu3min/patient.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package buffer; 4 | 5 | import "common.proto"; 6 | 7 | message PatientData { 8 | ManagingOrganization managingOrganization = 1; 9 | MaritalStatus maritalStatus = 2; 10 | repeated Name name = 3; 11 | bool deceasedBoolean = 4; 12 | string resourceType = 5; 13 | Text text = 6; 14 | repeated Address address = 7; 15 | repeated Communication communication = 8; 16 | string birthDate = 9; 17 | repeated Telecom telecom = 10; 18 | repeated Contact contact = 11; 19 | string gender = 12; 20 | bool active = 13; 21 | bool multipleBirthBoolean = 14; 22 | repeated Identifier identifier = 15; 23 | Meta meta = 16; 24 | string id = 17; 25 | } 26 | 27 | message MaritalStatus { 28 | string text = 1; 29 | repeated Coding coding = 2; 30 | } 31 | -------------------------------------------------------------------------------- /src/healthData/hash.js: -------------------------------------------------------------------------------- 1 | import { sha3 } from 'utils'; 2 | import reader from './reader'; 3 | import helper from './helper'; 4 | 5 | const hashData = (data, type, subType = null) => 6 | new Promise((resolve, reject) => { 7 | helper.makeBuffer(data, type, subType) 8 | .then(dataBuffer => resolve(sha3(dataBuffer))) 9 | .catch(err => reject(err)); 10 | }); 11 | 12 | const hashDataFromFile = (filePath, type, subType = null) => 13 | new Promise((resolve, reject) => { 14 | switch (type) { 15 | case 'dicom': 16 | resolve(reader.hashDataStream(filePath)); 17 | break; 18 | default: 19 | reader.readData(filePath) 20 | .then(data => resolve(hashData(data, type, subType))) 21 | .catch(err => reject(err)); 22 | } 23 | }); 24 | 25 | export default { 26 | hashData, 27 | hashDataFromFile, 28 | }; 29 | -------------------------------------------------------------------------------- /src/local/transaction/utils/setTx.js: -------------------------------------------------------------------------------- 1 | import binary from 'bops'; 2 | import { genPayloadBuf } from 'utils'; 3 | import { CHAIN_ID } from '../../../config'; 4 | import { PAYLOAD_TYPES } from './constants'; 5 | import * as jsonDescriptor from './proto/transaction.pb.json'; 6 | 7 | const defaultOptions = { 8 | chain_id: CHAIN_ID, 9 | from: null, 10 | nonce: 0, 11 | payload: undefined, 12 | to: null, 13 | type: null, 14 | value: '0', 15 | }; 16 | 17 | const setTx = (options) => { 18 | const opts = Object.assign({}, defaultOptions, options); 19 | const payloadType = PAYLOAD_TYPES[opts.type]; 20 | const payloadBuf = payloadType ? genPayloadBuf(opts.payload, payloadType, jsonDescriptor) : null; 21 | return { 22 | chain_id: opts.chain_id, 23 | from: opts.from, 24 | nonce: opts.nonce, 25 | payload: payloadBuf ? binary.to(payloadBuf, 'hex') : undefined, 26 | 27 | to: opts.to, 28 | tx_type: opts.type, 29 | value: opts.value, 30 | }; 31 | }; 32 | 33 | export default setTx; 34 | -------------------------------------------------------------------------------- /src/utils/hash.js: -------------------------------------------------------------------------------- 1 | import { sha3_256 as SHA3256 } from 'js-sha3'; 2 | 3 | const sha3 = (msg) => { 4 | let message = ''; 5 | switch (typeof msg) { 6 | case 'string': 7 | message = msg; 8 | break; 9 | case 'object': 10 | if (msg instanceof Uint8Array) { 11 | message = msg; 12 | } else { 13 | message = msg.toString(); 14 | } 15 | break; 16 | case 'number': 17 | message = msg.toString(); 18 | break; 19 | case 'Uint8Array': 20 | message = msg; 21 | break; 22 | default: 23 | throw new Error('Invalid msg type'); 24 | } 25 | return SHA3256.create().update(message).hex(); 26 | }; 27 | 28 | const sha3Stream = stream => 29 | new Promise((resolve) => { 30 | const hash = SHA3256.create(); 31 | stream.on('data', (data) => { 32 | hash.update(data); 33 | }); 34 | stream.on('end', () => { 35 | resolve(hash.hex()); 36 | }); 37 | }); 38 | 39 | export default { 40 | sha3, 41 | sha3Stream, 42 | }; 43 | -------------------------------------------------------------------------------- /test/healthData/samples/observation_sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Observation", 3 | "id": "f101", 4 | "status": "final", 5 | "category": [ 6 | { 7 | "coding": [ 8 | { 9 | "system": "http://hl7.org/fhir/observation-category", 10 | "code": "vital-signs", 11 | "display": "Vital Signs" 12 | } 13 | ] 14 | } 15 | ], 16 | "code": { 17 | "coding": [ 18 | { 19 | "system": "http://loinc.org", 20 | "code": "29463-7", 21 | "display": "Body Weight" 22 | }, 23 | { 24 | "system": "http://loinc.org", 25 | "code": "3141-9", 26 | "display": "Body weight Measured" 27 | } 28 | ] 29 | }, 30 | "subject": { 31 | "reference": "Patient/f100" 32 | }, 33 | "context": { 34 | "reference": "Encounter/f100" 35 | }, 36 | "effectiveDateTime": "2017-05-15", 37 | "valueQuantity": { 38 | "value": 78, 39 | "unit": "kg", 40 | "system": "http://unitsofmeasure.org", 41 | "code": "kg" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/local/transaction/utils/validateTx.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import validateTx from 'local/transaction/utils/validateTx'; 3 | 4 | describe('# validateTx', () => { 5 | const tx = { 6 | alg: 1, 7 | chain_id: 1, 8 | from: '0367e7dee7bb273147991cb1d2b99a4daf069064fb77bd9a70c7998c5f1a00d58c', 9 | nonce: 3, 10 | data: { type: 'binary' }, 11 | timestamp: 1530854902566, 12 | to: '037d91596727bc522553510b34815f382c2060cbb776f2765deafb48ae528d324b', 13 | value: 1, 14 | }; 15 | 16 | it('should return error when tx is not an object', () => { 17 | expect(() => validateTx('not object')).to.throw(Error, 'Transaction format should be object.'); 18 | }); 19 | it('should return error when tx has no required params', () => { 20 | expect(() => validateTx(tx, ['test'])).to.throw(Error, 'Transaction should have test field.'); 21 | }); 22 | it('should return error when tx has invalid value', () => { 23 | expect(() => validateTx(tx, ['value'])).to.throw(Error, 'Type of value need to be string'); 24 | }); 25 | }); 26 | 27 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=panaceajs 13 | 14 | if "%1" == "" goto help 15 | 16 | if "%1" == "clean" ( 17 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 18 | del /q /s %BUILDDIR%\* 19 | goto end 20 | ) 21 | 22 | %SPHINXBUILD% >NUL 2>NUL 23 | if errorlevel 9009 ( 24 | echo. 25 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 26 | echo.installed, then set the SPHINXBUILD environment variable to point 27 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 28 | echo.may add the Sphinx directory to PATH. 29 | echo. 30 | echo.If you don't have Sphinx installed, grab it from 31 | echo.http://sphinx-doc.org/ 32 | exit /b 1 33 | ) 34 | 35 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 36 | goto end 37 | 38 | :help 39 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 40 | 41 | :end 42 | popd 43 | -------------------------------------------------------------------------------- /test/local/transaction/utils/wrapTxCreator.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import wrapTxCreator from 'local/transaction/utils/wrapTxCreator'; 3 | 4 | describe('# wrapTxCreator', () => { 5 | const creator = rawTx => rawTx; 6 | const rawTxFields = { 7 | alg: 1, 8 | chain_id: 1, 9 | from: '0367e7dee7bb273147991cb1d2b99a4daf069064fb77bd9a70c7998c5f1a00d58c', 10 | nonce: 3, 11 | to: '037d91596727bc522553510b34815f382c2060cbb776f2765deafb48ae528d324b', 12 | value: '55', 13 | tx_type: 'transfer', 14 | }; 15 | let tx; 16 | 17 | beforeEach(() => { 18 | const wtc = wrapTxCreator(creator); 19 | tx = wtc(rawTxFields); 20 | return Promise.resolve(); 21 | }); 22 | 23 | it('should return object with hash', () => { 24 | expect(tx).to.have.property('hash'); 25 | }); 26 | it('should return object with rawTx', () => { 27 | expect(tx).to.have.property('rawTx') 28 | .to.eql(creator(rawTxFields)); 29 | }); 30 | it('should return object with sign', () => { 31 | expect(tx).to.have.property('sign') 32 | .to.equal(null); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/utils/proto.js: -------------------------------------------------------------------------------- 1 | import binary from 'bops'; 2 | import protobuf from 'protobufjs/light'; 3 | 4 | 5 | const genPayloadBuf = (payload, type, jsonDescriptor) => { 6 | if (payload === undefined || payload === null) return null; 7 | const root = protobuf.Root.fromJSON(jsonDescriptor); 8 | const PayloadTarget = root.lookupType(type.charAt(0).toUpperCase() + type.slice(1)); 9 | const errMsg = PayloadTarget.verify(payload); 10 | if (errMsg) throw Error(errMsg); 11 | 12 | const message = PayloadTarget.create(payload); 13 | return PayloadTarget.encode(message).finish(); 14 | }; 15 | 16 | const recoverFromPayload = (payload, type, jsonDescriptor) => { 17 | const payloadBuf = binary.from(payload, 'hex'); 18 | const root = protobuf.Root.fromJSON(jsonDescriptor); 19 | const PayloadTarget = root.lookupType(type.charAt(0).toUpperCase() + type.slice(1)); 20 | const payloadMessage = PayloadTarget.decode(payloadBuf); 21 | return PayloadTarget.toObject(payloadMessage, { 22 | enums: String, 23 | longs: Number, 24 | }); 25 | }; 26 | 27 | export default { 28 | genPayloadBuf, 29 | recoverFromPayload, 30 | }; 31 | -------------------------------------------------------------------------------- /src/client/apis/transaction.js: -------------------------------------------------------------------------------- 1 | import { parseTransaction, parseTransactions } from '../parser'; 2 | 3 | export default ({ sendRequest }) => { 4 | const getPendingTransactions = () => sendRequest({ 5 | method: 'get', 6 | parser: parseTransactions, 7 | path: 'v1/transactions/pending', 8 | }); 9 | 10 | const getTransaction = hash => sendRequest({ 11 | method: 'get', 12 | parser: parseTransaction, 13 | path: 'v1/transaction', 14 | payload: { 15 | hash, 16 | }, 17 | }); 18 | 19 | const getTransactionReceipt = hash => sendRequest({ 20 | method: 'get', 21 | path: 'v1/transaction/receipt', 22 | payload: { 23 | hash, 24 | }, 25 | }); 26 | 27 | const sendTransaction = tx => sendRequest({ 28 | method: 'post', 29 | path: 'v1/transaction', 30 | ...tx.rawTx && tx.hash && tx.sign && { 31 | payload: { 32 | ...tx.rawTx, 33 | hash: tx.hash, 34 | payer_sign: tx.payerSign, 35 | sign: tx.sign, 36 | }, 37 | }, 38 | }); 39 | 40 | return { 41 | getPendingTransactions, 42 | getTransaction, 43 | getTransactionReceipt, 44 | sendTransaction, 45 | }; 46 | }; 47 | -------------------------------------------------------------------------------- /test/local/transaction/tx_vest.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | recoverPayload, 4 | vestTx, 5 | } from 'local/transaction'; 6 | import { constants } from 'local/transaction/utils'; 7 | 8 | const { 9 | REQUIRED_TX_PARAMS, 10 | VEST, 11 | } = constants; 12 | 13 | describe('# vestTx', () => { 14 | const fields = { 15 | from: '02bdc97dfc02502c5b8301ff46cbbb0dce56cd96b0af75edc50560630de5b0a472', 16 | nonce: 1, 17 | value: '100000000', 18 | }; 19 | const tx = vestTx(fields); 20 | 21 | it('should return transaction contains hash', () => { 22 | expect(tx).to.have.property('hash'); 23 | }); 24 | it('should return transaction contains rawTx', () => { 25 | expect(tx).to.have.property('rawTx') 26 | .to.contain.all.keys(REQUIRED_TX_PARAMS[VEST].map(param => param.split('.')[0])); 27 | }); 28 | it('should return transaction not contains signature', () => { 29 | expect(tx).to.have.property('sign') 30 | .to.equal(null); 31 | }); 32 | 33 | describe('# recoverPayload', () => { 34 | it('should recover expected transaction payload', () => { 35 | expect(recoverPayload(tx)).to.eql(null); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/local/transaction/tx_quitCandidacy.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | quitCandidacyTx, 4 | recoverPayload, 5 | } from 'local/transaction'; 6 | import { constants } from 'local/transaction/utils'; 7 | 8 | const { 9 | REQUIRED_TX_PARAMS, 10 | QUIT_CANDIDATE, 11 | } = constants; 12 | 13 | describe('# quitCandidacyTx', () => { 14 | const fields = { 15 | from: '02bdc97dfc02502c5b8301ff46cbbb0dce56cd96b0af75edc50560630de5b0a472', 16 | nonce: 1, 17 | }; 18 | const tx = quitCandidacyTx(fields); 19 | 20 | it('should return transaction contains hash', () => { 21 | expect(tx).to.have.property('hash'); 22 | }); 23 | it('should return transaction contains rawTx', () => { 24 | expect(tx).to.have.property('rawTx') 25 | .to.contain.all.keys(REQUIRED_TX_PARAMS[QUIT_CANDIDATE].map(param => param.split('.')[0])); 26 | }); 27 | it('should return transaction not contains signature', () => { 28 | expect(tx).to.have.property('sign') 29 | .to.equal(null); 30 | }); 31 | 32 | describe('# recoverPayload', () => { 33 | it('should recover expected transaction payload', () => { 34 | expect(recoverPayload(tx)).to.eql(null); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/healthData/mhd.js: -------------------------------------------------------------------------------- 1 | import { genHexBuf } from 'utils'; 2 | import { BYTESIZES, TYPE, SUBTYPE, MHD_MAGICNUMBER } from './constants'; 3 | 4 | const makeMHDHeader = (opts) => { 5 | const magicNumberBuffer = genHexBuf(MHD_MAGICNUMBER, BYTESIZES.MAGICNUMBER); 6 | const versionBuffer = genHexBuf('0', BYTESIZES.VERSION); 7 | const typeNum = opts.type && TYPE[opts.type.toUpperCase()] ? TYPE[opts.type.toUpperCase()] : 0; 8 | const typeBuffer = genHexBuf(typeNum.toString(), BYTESIZES.TYPE); 9 | const subTypeNum = opts.subType && SUBTYPE[typeNum][opts.subType.toUpperCase()] ? 10 | SUBTYPE[typeNum][opts.subType.toUpperCase()] : 0; 11 | const subTypeBuffer = genHexBuf(subTypeNum.toString(), BYTESIZES.SUBTYPE); 12 | const hashBuffer = genHexBuf(opts.hash, BYTESIZES.HASH); 13 | const dataSizeBufffer = genHexBuf(opts.size.toString(), BYTESIZES.DATASIZE); 14 | return Buffer.concat([ 15 | magicNumberBuffer, 16 | versionBuffer, 17 | typeBuffer, 18 | subTypeBuffer, 19 | hashBuffer, 20 | dataSizeBufffer, 21 | ]); 22 | }; 23 | 24 | const makeMHD = opts => Buffer.concat([ 25 | makeMHDHeader(opts), 26 | opts.dataBuffer, 27 | ]); 28 | 29 | export default { 30 | makeMHDHeader, 31 | makeMHD, 32 | }; 33 | -------------------------------------------------------------------------------- /test/local/transaction/tx_becomeCandidate.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | becomeCandidateTx, 4 | recoverPayload, 5 | } from 'local/transaction'; 6 | import { constants } from 'local/transaction/utils'; 7 | 8 | const { 9 | REQUIRED_TX_PARAMS, 10 | BECOME_CANDIDATE, 11 | } = constants; 12 | 13 | describe('# becomeCandidateTx', () => { 14 | const fields = { 15 | from: '02bdc97dfc02502c5b8301ff46cbbb0dce56cd96b0af75edc50560630de5b0a472', 16 | nonce: 1, 17 | value: '100000000', 18 | }; 19 | const tx = becomeCandidateTx(fields); 20 | 21 | it('should return transaction contains hash', () => { 22 | expect(tx).to.have.property('hash'); 23 | }); 24 | it('should return transaction contains rawTx', () => { 25 | expect(tx).to.have.property('rawTx') 26 | .to.contain.all.keys(REQUIRED_TX_PARAMS[BECOME_CANDIDATE].map(param => param.split('.')[0])); 27 | }); 28 | it('should return transaction not contains signature', () => { 29 | expect(tx).to.have.property('sign') 30 | .to.equal(null); 31 | }); 32 | 33 | describe('# recoverPayload', () => { 34 | it('should recover expected transaction payload', () => { 35 | expect(recoverPayload(tx)).to.eql(null); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /test/local/transaction/tx_withdrawVesting.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | recoverPayload, 4 | withdrawVestingTx, 5 | } from 'local/transaction'; 6 | import { constants } from 'local/transaction/utils'; 7 | 8 | const { 9 | REQUIRED_TX_PARAMS, 10 | WITHDRAW_VESTING, 11 | } = constants; 12 | 13 | describe('# withdrawVestingTx', () => { 14 | const fields = { 15 | from: '02bdc97dfc02502c5b8301ff46cbbb0dce56cd96b0af75edc50560630de5b0a472', 16 | nonce: 1, 17 | value: '100000000', 18 | }; 19 | const tx = withdrawVestingTx(fields); 20 | 21 | it('should return transaction contains hash', () => { 22 | expect(tx).to.have.property('hash'); 23 | }); 24 | it('should return transaction contains rawTx', () => { 25 | expect(tx).to.have.property('rawTx') 26 | .to.contain.all.keys(REQUIRED_TX_PARAMS[WITHDRAW_VESTING].map(param => param.split('.')[0])); 27 | }); 28 | it('should return transaction not contains signature', () => { 29 | expect(tx).to.have.property('sign') 30 | .to.equal(null); 31 | }); 32 | 33 | describe('# recoverPayload', () => { 34 | it('should recover expected transaction payload', () => { 35 | expect(recoverPayload(tx)).to.eql(null); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/identification/certificate.js: -------------------------------------------------------------------------------- 1 | import { sign, verifySignature } from 'cryptography'; 2 | import { sha3 } from 'utils'; 3 | 4 | const createCertificate = ({ 5 | expireDate, 6 | issuer, // highly recommend use webpage url as an issuer 7 | issuerAccount, 8 | issueDate, 9 | passphrase = '', 10 | pubKey, 11 | }) => { 12 | if (!(expireDate > issueDate)) throw new Error('Expire date should be later than issue date.'); 13 | 14 | const certificate = { 15 | expireDate, 16 | issuer, 17 | issueDate, 18 | pubKey, 19 | }; 20 | const certificateHash = sha3(certificate); 21 | const privKey = issuerAccount.getDecryptedPrivateKey(passphrase); 22 | certificate.signature = sign(privKey, certificateHash); 23 | 24 | return certificate; 25 | }; 26 | 27 | const verifyCertificate = (certificate, timeStamp, issuerPubKey) => { 28 | // check timestamp 29 | if (certificate.issueDate > timeStamp || certificate.expireDate < timeStamp) return false; 30 | 31 | // check signature owner 32 | if (!verifySignature( 33 | issuerPubKey, 34 | sha3(certificate), 35 | certificate.signature, 36 | )) return false; 37 | 38 | return true; 39 | }; 40 | 41 | export default { 42 | createCertificate, 43 | verifyCertificate, 44 | }; 45 | -------------------------------------------------------------------------------- /src/cryptography/sign.js: -------------------------------------------------------------------------------- 1 | import secp256k1 from 'secp256k1'; 2 | 3 | const recoverPubKeyFromSignature = (msgHash, signature) => { 4 | const recovery = parseInt(signature.slice(-2), 16); 5 | const msgHashBuffer = Buffer.from(msgHash, 'hex'); 6 | const sigBuffer = Buffer.from(signature.slice(0, -2), 'hex'); 7 | 8 | return secp256k1.recover(msgHashBuffer, sigBuffer, recovery).toString('hex'); 9 | }; 10 | 11 | const sign = (privKey, msgHash) => { 12 | const msgHashBuffer = Buffer.from(msgHash, 'hex'); 13 | const privKeyBuffer = Buffer.from(privKey, 'hex'); 14 | const signature = secp256k1.sign(msgHashBuffer, privKeyBuffer); 15 | const recoveryCode = Buffer.alloc(1); 16 | recoveryCode.writeIntBE(signature.recovery, 0, 1); 17 | 18 | return signature.signature.toString('hex') + recoveryCode.toString('hex'); 19 | }; 20 | 21 | const verifySignature = (pubKey, msgHash, signature) => { 22 | const msgHashBuffer = Buffer.from(msgHash, 'hex'); 23 | const pubKeyBuffer = Buffer.from(pubKey, 'hex'); 24 | const signatureBuffer = Buffer.from(signature.slice(0, -2), 'hex'); 25 | 26 | return secp256k1.verify(msgHashBuffer, signatureBuffer, pubKeyBuffer); 27 | }; 28 | 29 | export default { 30 | recoverPubKeyFromSignature, 31 | sign, 32 | verifySignature, 33 | }; 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # panacea-js 2 | 3 | Official client-side JavaScript library for the [medibloc blockchain](https://github.com/medibloc/go-medibloc). 4 | 5 | ## Install 6 | 7 | ### Node.js 8 | 9 | ```bash 10 | npm install @medibloc/panacea-js 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```javascript 16 | var Panaceajs = require('@medibloc/panacea-js'); 17 | var panaceajs = Panaceajs.init(['http://localhost:9921']); 18 | ``` 19 | 20 | Please refers to the [documentation](https://panacea-js.readthedocs.io/en/latest/) 21 | 22 | ## Test 23 | 24 | ```bash 25 | npm test 26 | ``` 27 | 28 | ### Demo 29 | [Demo video link](https://youtu.be/igmLEfxw-u8) 30 | 31 | ## License 32 | 33 | ``` 34 | Copyright 2018 MediBloc 35 | 36 | Licensed under the Apache License, Version 2.0 (the "License"); 37 | you may not use this file except in compliance with the License. 38 | You may obtain a copy of the License at 39 | 40 | http://www.apache.org/licenses/LICENSE-2.0 41 | 42 | Unless required by applicable law or agreed to in writing, software 43 | distributed under the License is distributed on an "AS IS" BASIS, 44 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 45 | See the License for the specific language governing permissions and 46 | limitations under the License. 47 | ``` 48 | -------------------------------------------------------------------------------- /src/healthData/proto/stu3/primitive_has_no_value.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.fhir.stu3.proto; 18 | 19 | option java_multiple_files = true; 20 | option java_package = "com.google.fhir.stu3.proto"; 21 | 22 | import "proto/stu3/annotations.proto"; 23 | import "proto/stu3/datatypes.proto"; 24 | 25 | // This is a Google extension described by 26 | // https://g.co/fhir/StructureDefinition/primitiveHasNoValue 27 | message PrimitiveHasNoValue { 28 | option (fhir_extension_url) = 29 | "https://g.co/fhir/StructureDefinition/primitiveHasNoValue"; 30 | 31 | // The value. 32 | Boolean value_boolean = 1; 33 | } 34 | -------------------------------------------------------------------------------- /test/local/transaction/tx_vote.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | createVotePayload, 4 | recoverPayload, 5 | voteTx, 6 | } from 'local/transaction'; 7 | import { constants } from 'local/transaction/utils'; 8 | 9 | const { 10 | REQUIRED_TX_PARAMS, 11 | VOTE, 12 | } = constants; 13 | 14 | describe('# voteTx', () => { 15 | const payload = createVotePayload(['02bdc97dfc02502c5b8301ff46cbbb0dce56cd96b0af75edc50560630de5b0a472']); 16 | const fields = { 17 | from: '02bdc97dfc02502c5b8301ff46cbbb0dce56cd96b0af75edc50560630de5b0a472', 18 | nonce: 1, 19 | payload, 20 | }; 21 | const tx = voteTx(fields); 22 | 23 | it('should return transaction contains hash', () => { 24 | expect(tx).to.have.property('hash'); 25 | }); 26 | it('should return transaction contains rawTx', () => { 27 | expect(tx).to.have.property('rawTx') 28 | .to.contain.all.keys(REQUIRED_TX_PARAMS[VOTE].map(param => param.split('.')[0])); 29 | }); 30 | it('should return transaction not contains signature', () => { 31 | expect(tx).to.have.property('sign') 32 | .to.equal(null); 33 | }); 34 | 35 | describe('# recoverPayload', () => { 36 | it('should recover expected transaction payload', () => { 37 | expect(recoverPayload(tx)).to.eql(payload); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/local/transaction/utils/checkTx.js: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | 3 | const MAX_VALUE = new BigNumber('ffffffffffffffffffffffffffffffff', 16); 4 | 5 | const checkObject = (tx) => { 6 | if (typeof tx !== 'object') throw new Error('Transaction format should be object.'); 7 | }; 8 | 9 | const checkRequiredParams = (tx, requiredParams) => { 10 | requiredParams.forEach((param) => { 11 | const p = param.split('.'); 12 | if (p.length === 1 && tx[p[0]] === undefined) { 13 | throw new Error(`Transaction should have ${param} field.`); 14 | } else if (p.length === 2 && 15 | (tx[p[0]] === undefined || tx[p[0]][p[1]] === undefined)) { 16 | throw new Error(`Transaction should have ${param} field.`); 17 | } else if (p.length > 2) { 18 | throw new Error('Transaction should have field with max 2 depths.'); 19 | } 20 | }); 21 | }; 22 | 23 | const checkValue = (tx) => { 24 | if (typeof tx.value !== 'string') throw new Error('Type of value need to be string'); 25 | const value = new BigNumber(tx.value); // From Decimal 26 | if (value.lt(0)) throw new Error('Can not send negative value'); 27 | if (value.gt(MAX_VALUE)) throw new Error('Amount is too large'); 28 | }; 29 | 30 | export default { 31 | checkObject, 32 | checkRequiredParams, 33 | checkValue, 34 | }; 35 | -------------------------------------------------------------------------------- /src/local/transaction/utils/hashTx.js: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import binary from 'bops'; 3 | import { sha3_256 as SHA3256 } from 'js-sha3'; 4 | import protobuf from 'protobufjs/light'; 5 | import { genHexBuf } from 'utils'; 6 | import { BYTESIZES } from './constants'; 7 | import * as jsonDescriptor from './proto/transaction.pb.json'; 8 | 9 | const hashTx = (tx) => { 10 | // TODO @ggomma defaultPayload string check 11 | const txHashTarget = { 12 | txType: tx.tx_type, 13 | from: genHexBuf(tx.from, BYTESIZES.ADDRESS), 14 | to: genHexBuf(tx.to ? tx.to : '', BYTESIZES.ADDRESS), 15 | value: genHexBuf(tx.value ? BigNumber(tx.value).toString(16) : '', BYTESIZES.VALUE), 16 | nonce: tx.nonce, 17 | chainId: tx.chain_id, 18 | payload: (tx.payload === undefined || tx.payload === '') ? null : binary.from(tx.payload, 'hex'), 19 | }; 20 | 21 | const root = protobuf.Root.fromJSON(jsonDescriptor); 22 | const TxHashTarget = root.lookupType('TransactionHashTarget'); 23 | const errMsg = TxHashTarget.verify(txHashTarget); 24 | if (errMsg) throw Error(errMsg); 25 | 26 | const message = TxHashTarget.create(txHashTarget); 27 | const buf = TxHashTarget.encode(message).finish(); 28 | const hash = SHA3256.create(); 29 | return hash.update(buf).hex(); 30 | }; 31 | 32 | export default hashTx; 33 | -------------------------------------------------------------------------------- /src/healthData/proto/stu3/structuredefinition_regex.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.fhir.stu3.proto; 18 | 19 | import "proto/stu3/annotations.proto"; 20 | import "proto/stu3/datatypes.proto"; 21 | 22 | option java_multiple_files = true; 23 | option java_package = "com.google.fhir.stu3.proto"; 24 | 25 | // Auto-generated from StructureDefinition for regex. 26 | // Regex applies to the value. 27 | message StructureDefinitionRegex { 28 | option (structure_definition_kind) = KIND_COMPLEX_TYPE; 29 | option (fhir_extension_url) = 30 | "http://hl7.org/fhir/StructureDefinition/structuredefinition-regex"; 31 | 32 | // Value of extension 33 | String value_string = 1; 34 | } 35 | -------------------------------------------------------------------------------- /src/healthData/proto/stu3/elementdefinition_binding_name.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.fhir.stu3.proto; 18 | 19 | import "proto/stu3/annotations.proto"; 20 | import "proto/stu3/datatypes.proto"; 21 | 22 | option java_multiple_files = true; 23 | option java_package = "com.google.fhir.stu3.proto"; 24 | 25 | // Auto-generated from StructureDefinition for bindingName. 26 | // Suggested Name for code generation. 27 | message ElementDefinitionBindingName { 28 | option (structure_definition_kind) = KIND_COMPLEX_TYPE; 29 | option (fhir_extension_url) = 30 | "http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName"; 31 | 32 | // Value of extension 33 | String value_string = 1; 34 | } 35 | -------------------------------------------------------------------------------- /src/healthData/proto/stu3/base64_separator_stride.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.fhir.stu3.proto; 18 | 19 | option java_multiple_files = true; 20 | option java_package = "com.google.fhir.stu3.proto"; 21 | 22 | import "proto/stu3/annotations.proto"; 23 | import "proto/stu3/datatypes.proto"; 24 | 25 | // This is a Google extension described by 26 | // https://g.co/fhir/StructureDefinition/base64Binary-separatorStride 27 | message Base64SeparatorStride { 28 | option (fhir_extension_url) = 29 | "https://g.co/fhir/StructureDefinition/base64Binary-separatorStride"; 30 | 31 | // The separator, usually a sequence of spaces. 32 | String separator = 1; 33 | 34 | // The stride. 35 | PositiveInt stride = 2; 36 | } 37 | -------------------------------------------------------------------------------- /src/healthData/proto/stu3/structuredefinition_explicit_type_name.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | package google.fhir.stu3.proto; 18 | 19 | import "proto/stu3/annotations.proto"; 20 | import "proto/stu3/datatypes.proto"; 21 | 22 | option java_multiple_files = true; 23 | option java_package = "com.google.fhir.stu3.proto"; 24 | 25 | // Auto-generated from StructureDefinition for explicit-type-name. 26 | // Advisory - name of Type for implementations. 27 | message StructureDefinitionExplicitTypeName { 28 | option (structure_definition_kind) = KIND_COMPLEX_TYPE; 29 | option (fhir_extension_url) = 30 | "http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name"; 31 | 32 | // Value of extension 33 | String value_string = 1; 34 | } 35 | -------------------------------------------------------------------------------- /docs/panaceajs.rst: -------------------------------------------------------------------------------- 1 | .. _panaceajs: 2 | 3 | .. include:: include_announcement.rst 4 | 5 | ===== 6 | panaceajs 7 | ===== 8 | 9 | .. code-block:: javascript 10 | 11 | var Panaceajs = require('@medibloc/panacea-js'); 12 | var panaceajs = Panaceajs.init(['http://localhost:9921']); 13 | 14 | 15 | The ``panaceajs`` object has following objects. 16 | 17 | - The :ref:`client ` object allows you to interact with MediBloc blockchain. 18 | - The :ref:`cryptography ` object contains cryptographic functions. 19 | - The :ref:`local.Account ` object contains functions to generate MediBloc accounts, which contain encrypted private key and public key and can induce public key from the private key. 20 | - The :ref:`local.transaction ` object contains functions to generate transaction. 21 | - The :ref:`healthData ` object helps to encode and decode the health data as :ref:`MHD format `. 22 | - The :ref:`identification ` contains identification functions. 23 | - The :ref:`utils ` object provides utility functions for panaceajs. 24 | 25 | Parameters 26 | ---------- 27 | 28 | ``nodes`` - ``Array``: The array of node URL that will be used for the request. 29 | 30 | .. Hint:: 31 | The panaceajs client sends a request to one of the nodes. If the request fails, it automatically retries the request to another node. 32 | 33 | .. include:: include_blockchain_note.rst 34 | -------------------------------------------------------------------------------- /test/utils/hash.js: -------------------------------------------------------------------------------- 1 | import { Readable } from 'stream'; 2 | import chai, { expect } from 'chai'; 3 | import utils from 'utils'; 4 | import chaiHexString from 'test/helpers/chaiHexString'; 5 | 6 | chai.use(chaiHexString); 7 | 8 | describe('#hash', () => { 9 | const msg = 'hello MeDiBlOc123!@#'; 10 | const expectedHash = '8a1fb1154b917c9e3df4370008e0bf34c6de6babb1592225371731a71a9b2e13'; 11 | 12 | describe('#sha3', () => { 13 | let msgHash; 14 | beforeEach(() => { 15 | msgHash = utils.sha3(msg); 16 | return Promise.resolve(); 17 | }); 18 | 19 | it('should generate hex format string', () => { 20 | expect(msgHash).to.be.hexString 21 | .and.to.be.equal(expectedHash); 22 | }); 23 | 24 | it('should generate unique hash for the message', () => { 25 | const msgTemp = 'hello medibloc123!@#'; 26 | const msgTempHash = utils.sha3(msgTemp); 27 | expect(msgHash).not.to.be.equal(msgTempHash); 28 | }); 29 | }); 30 | 31 | describe('#sha3Stream', () => { 32 | let msgHash; 33 | beforeEach(async () => { 34 | const inStream = new Readable(); 35 | inStream.push(msg); 36 | inStream.push(null); 37 | msgHash = await utils.sha3Stream(inStream); 38 | return Promise.resolve(); 39 | }); 40 | 41 | it('should generate hex format string', () => { 42 | expect(msgHash).to.be.hexString 43 | .and.to.be.equal(expectedHash); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/local/transaction/tx_dataUpload.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | createDataPayload, 4 | dataUploadTx, 5 | recoverPayload, 6 | } from 'local/transaction'; 7 | import { constants } from 'local/transaction/utils'; 8 | 9 | const { 10 | REQUIRED_TX_PARAMS, 11 | DATA_UPLOAD, 12 | } = constants; 13 | 14 | describe('# dataUploadTx', () => { 15 | const payload = createDataPayload('487b69767e201f485a67b915f1726e39a9d84d72ce3753dfdc824ebdf22e9b33'); 16 | const fields = { 17 | from: '02bdc97dfc02502c5b8301ff46cbbb0dce56cd96b0af75edc50560630de5b0a472', 18 | nonce: 1, 19 | payload, 20 | }; 21 | const tx = dataUploadTx(fields); 22 | const txHashFromGo = '0d61ed6b9f36b31073e4be48632803b119ebc73d6b26146309e32cdca5b3a7e9'; 23 | 24 | it('should return transaction contains hash', () => { 25 | expect(tx).to.have.property('hash') 26 | .to.equal(txHashFromGo); 27 | }); 28 | it('should return transaction contains rawTx', () => { 29 | expect(tx).to.have.property('rawTx') 30 | .to.contain.all.keys(REQUIRED_TX_PARAMS[DATA_UPLOAD].map(param => param.split('.')[0])); 31 | }); 32 | it('should return transaction not contains signature', () => { 33 | expect(tx).to.have.property('sign') 34 | .to.equal(null); 35 | }); 36 | 37 | describe('# recoverPayload', () => { 38 | it('should recover expected transaction payload', () => { 39 | expect(recoverPayload(tx)).to.eql(payload); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/cryptography/sign.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import cryptography from 'cryptography'; 3 | import { sha3 } from 'utils'; 4 | import chaiHexString from 'test/helpers/chaiHexString'; 5 | 6 | chai.use(chaiHexString); 7 | 8 | // sign and verify 9 | describe('#sign / #verifySignature', () => { 10 | // Sign for hashed msg 11 | const msg = 'hello MeDiBlOc123!@#'; 12 | const msgHash = sha3(msg); 13 | const keyPair = cryptography.getKeyPair(); 14 | describe('should generate signature for the input message and private key', () => { 15 | it('It should generate hex format string signature', () => { 16 | const signature = cryptography.sign(keyPair.privKey, msgHash); 17 | expect(signature).to.be.hexString; 18 | }); 19 | }); 20 | describe('should verify the owner of the signature', () => { 21 | it('Signature should be matched with the owner\'s public key', () => { 22 | const signature = cryptography.sign(keyPair.privKey, msgHash); 23 | const isValid = cryptography.verifySignature(keyPair.pubKey, msgHash, signature); 24 | expect(isValid).to.be.true; 25 | }); 26 | it('Signature should not be matehced with other\'s public key', () => { 27 | const signature = cryptography.sign(keyPair.privKey, msgHash); 28 | const anonymousPubKey = cryptography.getKeyPair().pubKey; 29 | const isValid = cryptography.verifySignature(anonymousPubKey, msgHash, signature); 30 | expect(isValid).to.be.false; 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/local/transaction/tx_revokeCertification.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | revokeCertificationTx, 4 | createRevokeCertificationPayload, 5 | recoverPayload, 6 | } from 'local/transaction'; 7 | import { constants } from 'local/transaction/utils'; 8 | 9 | const { 10 | REQUIRED_TX_PARAMS, 11 | ADD_CERTIFICATION, 12 | } = constants; 13 | 14 | describe('# addCertificationCandidateTx', () => { 15 | const payload = createRevokeCertificationPayload('487b69767e201f485a67b915f1726e39a9d84d72ce3753dfdc824ebdf22e9b33'); 16 | const fields = { 17 | from: '02bdc97dfc02502c5b8301ff46cbbb0dce56cd96b0af75edc50560630de5b0a472', 18 | nonce: 1, 19 | payload, 20 | to: '037d91596727bc522553510b34815f382c2060cbb776f2765deafb48ae528d324b', 21 | }; 22 | const tx = revokeCertificationTx(fields); 23 | 24 | it('should return transaction contains hash', () => { 25 | expect(tx).to.have.property('hash'); 26 | }); 27 | it('should return transaction contains rawTx', () => { 28 | expect(tx).to.have.property('rawTx') 29 | .to.contain.all.keys(REQUIRED_TX_PARAMS[ADD_CERTIFICATION].map(param => param.split('.')[0])); 30 | }); 31 | it('should return transaction not contains signature', () => { 32 | expect(tx).to.have.property('sign') 33 | .to.equal(null); 34 | }); 35 | 36 | describe('# recoverPayload', () => { 37 | it('should recover expected transaction payload', () => { 38 | expect(recoverPayload(tx)).to.eql(payload); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/healthData/proto/stu3min/careplan.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package buffer; 4 | 5 | import "common.proto"; 6 | 7 | message CarePlan { 8 | string status = 1; 9 | repeated Addresses addresses = 2; 10 | string resourceType = 3; 11 | Text text = 4; 12 | repeated CGoal goal = 5; 13 | Period period = 6; 14 | repeated CContained contained = 7; 15 | string intent = 8; 16 | repeated Activity activity = 9; 17 | repeated CareTeam careTeam = 10; 18 | string id = 11; 19 | Subject subject = 12; 20 | Meta meta = 13; 21 | } 22 | 23 | message CareTeam { 24 | string reference = 1; 25 | } 26 | 27 | message Activity { 28 | Detail detail = 1; 29 | } 30 | 31 | message Detail { 32 | Category category = 1; 33 | string status = 2; 34 | Code code = 3; 35 | ProductReference productReference = 4; 36 | string scheduledString = 5; 37 | bool prohibited = 6; 38 | DailyAmount dailyAmount = 7; 39 | } 40 | 41 | message DailyAmount { 42 | string code = 1; 43 | int64 value = 2; 44 | string unit = 3; 45 | string system = 4; 46 | } 47 | 48 | message ProductReference { 49 | string display = 1; 50 | string reference = 2; 51 | } 52 | 53 | message CContained { 54 | string resourceType = 1; 55 | repeated CParticipant participant = 2; 56 | string id = 3; 57 | } 58 | 59 | message CParticipant { 60 | Member member = 1; 61 | Role role = 2; 62 | } 63 | 64 | message Member { 65 | string display = 1; 66 | string reference = 2; 67 | } 68 | 69 | message CGoal { 70 | string reference = 1; 71 | } -------------------------------------------------------------------------------- /test/local/transaction/tx_addCertification.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | addCertificationTx, 4 | createAddCertificationPayload, 5 | recoverPayload, 6 | } from 'local/transaction'; 7 | import { constants } from 'local/transaction/utils'; 8 | 9 | const { 10 | REQUIRED_TX_PARAMS, 11 | ADD_CERTIFICATION, 12 | } = constants; 13 | 14 | describe('# addCertificationCandidateTx', () => { 15 | const payload = createAddCertificationPayload({ 16 | issueTime: 1540000000, 17 | expirationTime: 1600000000, 18 | hash: '487b69767e201f485a67b915f1726e39a9d84d72ce3753dfdc824ebdf22e9b33', 19 | }); 20 | const fields = { 21 | from: '02bdc97dfc02502c5b8301ff46cbbb0dce56cd96b0af75edc50560630de5b0a472', 22 | nonce: 1, 23 | payload, 24 | to: '037d91596727bc522553510b34815f382c2060cbb776f2765deafb48ae528d324b', 25 | }; 26 | const tx = addCertificationTx(fields); 27 | 28 | it('should return transaction contains hash', () => { 29 | expect(tx).to.have.property('hash'); 30 | }); 31 | it('should return transaction contains rawTx', () => { 32 | expect(tx).to.have.property('rawTx') 33 | .to.contain.all.keys(REQUIRED_TX_PARAMS[ADD_CERTIFICATION].map(param => param.split('.')[0])); 34 | }); 35 | it('should return transaction not contains signature', () => { 36 | expect(tx).to.have.property('sign') 37 | .to.equal(null); 38 | }); 39 | 40 | describe('# recoverPayload', () => { 41 | it('should recover expected transaction payload', () => { 42 | expect(recoverPayload(tx)).to.eql(payload); 43 | }); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/healthData/reader.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import jsonfile from 'jsonfile'; 4 | import { sha3Stream } from 'utils'; 5 | 6 | const getFilePath = (filePath) => { 7 | let newFilePath = filePath; 8 | if (!path.isAbsolute(filePath)) { 9 | newFilePath = path.resolve(__dirname, filePath); 10 | } 11 | return newFilePath; 12 | }; 13 | 14 | const getFileSize = filePath => 15 | new Promise((resolve, reject) => { 16 | const newFilePath = getFilePath(filePath); 17 | fs.stat(newFilePath, (err, stats) => { 18 | if (err) { 19 | reject(err); 20 | } 21 | resolve(stats.size); 22 | }); 23 | }); 24 | 25 | const hashDataStream = filePath => 26 | new Promise((resolve) => { 27 | const stream = fs.createReadStream(getFilePath(filePath)); 28 | sha3Stream(stream).then(hash => resolve(hash)); 29 | }); 30 | 31 | const readData = filePath => 32 | new Promise((resolve, reject) => { 33 | const newFilePath = getFilePath(filePath); 34 | const ext = path.extname(newFilePath); 35 | // TODO: support other extensions 36 | // TODO: check file size 37 | switch (ext) { 38 | case '.json': 39 | resolve(jsonfile.readFileSync(newFilePath)); 40 | break; 41 | case '.txt': 42 | resolve(fs.readFileSync(newFilePath)); 43 | break; 44 | case '.dcm': 45 | resolve(fs.readFileSync(newFilePath)); 46 | break; 47 | default: 48 | reject(new Error('not supporting extension')); 49 | } 50 | }); 51 | 52 | export default { 53 | getFileSize, 54 | hashDataStream, 55 | readData, 56 | }; 57 | -------------------------------------------------------------------------------- /docs/panaceajs-tutorial.rst: -------------------------------------------------------------------------------- 1 | .. _panaceajs-tutorial: 2 | 3 | .. include:: include_announcement.rst 4 | 5 | ======== 6 | Tutorial 7 | ======== 8 | 9 | This section handles several examples to use panaceajs. 10 | 11 | .. code-block:: javascript 12 | 13 | var Panaceajs = require('@medibloc/panacea-js'); 14 | var panaceajs = Panaceajs.init(['http://localhost:9921']); 15 | var Account = panaceajs.local.Account; 16 | var Client = panaceajs.client; 17 | var HealthData = panaceajs.healthData; 18 | var Transaction = panaceajs.local.transaction; 19 | 20 | --------------------------------------------------------------------------- 21 | 22 | send data upload transaction 23 | ============================ 24 | 25 | .. code-block:: javascript 26 | 27 | // create a new account 28 | var account = new Account(); 29 | // get account state 30 | Client.getAccount(account.pubKey, null, 'tail').then((res) => { 31 | var nonce = parseInt(res.nonce, 10); 32 | 33 | // calculate hash of the medical data file 34 | HealthData.hashDataFromFile('/file/path', 'medical-fhir', 'observation').then((hash) => { 35 | // creating a medical data payload 36 | var healthDataPayload = Transaction.createDataPayload(hash); 37 | 38 | // creating a medical data upload transaction 39 | var tx = Transaction.dataUploadTx({ 40 | from: account.pubKey, 41 | payload: healthDataPayload, 42 | nonce: nonce + 1 43 | }); 44 | 45 | // sign transaction 46 | account.signTx(tx); 47 | 48 | // send transaction 49 | Client.sendTransaction(tx).then((res2) => { 50 | // .. do something 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/healthData/constants.js: -------------------------------------------------------------------------------- 1 | export const BYTESIZES = { 2 | MAGICNUMBER: 4, 3 | VERSION: 2, 4 | TYPE: 2, 5 | SUBTYPE: 2, 6 | HASH: 32, 7 | DATASIZE: 6, 8 | }; 9 | export const OFFSETS = { 10 | MAGICNUMBER: 0, 11 | VERSION: 4, 12 | TYPE: 6, 13 | SUBTYPE: 8, 14 | HASH: 10, 15 | DATASIZE: 42, 16 | DATA: 48, 17 | }; 18 | export const MHD_MAGICNUMBER = '004d4844'; 19 | export const TYPE = { 20 | UNKNOWN: 0, 21 | 'MEDICAL-FHIR': 1, 22 | 'CLAIM-FHIR': 2, 23 | DICOM: 3, 24 | GENOMICS: 4, 25 | PGHD: 5, 26 | }; 27 | export const TYPE_REV = { 28 | 0: 'unknown', 29 | 1: 'medical-fhir', 30 | 2: 'claim-fhir', 31 | 3: 'dicom', 32 | 4: 'genomics', 33 | 5: 'pghd', 34 | }; 35 | export const SUBTYPE = [ 36 | { 37 | // unknown 38 | NULL: 0, 39 | }, 40 | { 41 | // medical-fhir 42 | NULL: 0, 43 | PATIENT: 1, 44 | OBSERVATION: 2, 45 | CAREPLAN: 3, 46 | }, 47 | { 48 | // claim-fhir 49 | NULL: 0, 50 | CLAIM: 1, 51 | }, 52 | { 53 | // dicom 54 | NULL: 0, 55 | }, 56 | { 57 | // genomics 58 | NULL: 0, 59 | VCF: 1, 60 | BAM: 2, 61 | }, 62 | { 63 | // pghd 64 | NULL: 0, 65 | }, 66 | ]; 67 | export const SUBTYPE_REV = [ 68 | { 69 | // unknown 70 | 0: 'null', 71 | }, 72 | { 73 | // medical-fhir 74 | 0: 'null', 75 | 1: 'patient', 76 | 2: 'observation', 77 | 3: 'claim', 78 | 4: 'careplan', 79 | }, 80 | { 81 | // claim-fhir 82 | 0: 'null', 83 | }, 84 | { 85 | // dicom 86 | 0: 'null', 87 | }, 88 | { 89 | // genomics 90 | 0: 'null', 91 | 1: 'vcf', 92 | 2: 'bam', 93 | }, 94 | { 95 | // pghd 96 | 0: 'null', 97 | }, 98 | ]; 99 | -------------------------------------------------------------------------------- /src/cryptography/keyGen.js: -------------------------------------------------------------------------------- 1 | import { createECDH, pbkdf2Sync, randomBytes } from 'crypto'; 2 | import { sha3_256 as SHA3256 } from 'js-sha3'; 3 | import secp256k1 from 'secp256k1'; 4 | import unorm from 'unorm'; 5 | 6 | const concatKeys = (string1, string2) => string1.concat(string2); 7 | 8 | const getKeyPair = () => { 9 | let privKey = ''; 10 | let check = false; 11 | while (check === false) { 12 | privKey = SHA3256(randomBytes(32) + randomBytes(32)); 13 | check = secp256k1.privateKeyVerify(Buffer.from(privKey, 'hex')); 14 | } 15 | 16 | const ec = createECDH('secp256k1'); 17 | ec.setPrivateKey(privKey, 'hex'); 18 | 19 | return { 20 | privKey, 21 | pubKey: ec.getPublicKey('hex', 'compressed'), 22 | }; 23 | }; 24 | 25 | const getPubKey = (privKey) => { 26 | const privKeyBuffer = Buffer.from(privKey, 'hex'); 27 | try { 28 | return secp256k1.publicKeyCreate(privKeyBuffer).toString('hex'); 29 | } catch (err) { 30 | throw new Error('Wrong format of private key'); 31 | } 32 | }; 33 | 34 | const getKeyPairFromPassphrase = (passphrase) => { 35 | const passphraseBuffer = Buffer.from(unorm.nfkd(passphrase), 'utf8'); 36 | const privKey = pbkdf2Sync(passphraseBuffer, 'medibloc', 32768, 32, 'sha512').toString('hex'); 37 | 38 | return { 39 | privKey, 40 | pubKey: getPubKey(privKey), 41 | }; 42 | }; 43 | 44 | const getSharedSecretKey = (privKey, pubKey) => { 45 | const ec = createECDH('secp256k1'); 46 | ec.generateKeys(); 47 | ec.setPrivateKey(privKey, 'hex'); 48 | return ec.computeSecret(pubKey, 'hex', 'hex'); 49 | }; 50 | 51 | export default { 52 | concatKeys, 53 | getKeyPair, 54 | getKeyPairFromPassphrase, 55 | getPubKey, 56 | getSharedSecretKey, 57 | }; 58 | -------------------------------------------------------------------------------- /src/local/transaction/index.js: -------------------------------------------------------------------------------- 1 | import createAddCertificationTx from './tx_addCertification'; 2 | import createBecomeCandidateTx from './tx_becomeCandidate'; 3 | import createDataUploadTx from './tx_dataUpload'; 4 | import createQuitCandidacyTx from './tx_quitCandidacy'; 5 | import createRevokeCertificationTx from './tx_revokeCertification'; 6 | import createValueTransferTx from './tx_valueTransfer'; 7 | import createVestTx from './tx_vest'; 8 | import createVoteTx from './tx_vote'; 9 | import createWithdrawVestingTx from './tx_withdrawVesting'; 10 | import payload from './payload'; 11 | 12 | /** 13 | * - becomeCandidateTx required fields 14 | * { from, nonce, value } 15 | * 16 | * - dataUploadTx required fields 17 | * { data - { payload }, from, nonce } 18 | * 19 | * - quitCandidacyTx required fields 20 | * { from, nonce } 21 | * 22 | * - valueTransferTx required fields 23 | * { from, nonce, to, value } 24 | * 25 | * - vestTx required fields 26 | * { from, nonce, value } 27 | * 28 | * - voteTx required fields 29 | * { from, nonce, to } 30 | * 31 | * - withdrawVestingTx required fields 32 | * { from, nonce, value } 33 | * 34 | */ 35 | export default { 36 | addCertificationTx: fields => createAddCertificationTx(fields), 37 | becomeCandidateTx: fields => createBecomeCandidateTx(fields), 38 | dataUploadTx: fields => createDataUploadTx(fields), 39 | quitCandidacyTx: fields => createQuitCandidacyTx(fields), 40 | revokeCertificationTx: fields => createRevokeCertificationTx(fields), 41 | valueTransferTx: fields => createValueTransferTx(fields), 42 | vestTx: fields => createVestTx(fields), 43 | voteTx: fields => createVoteTx(fields), 44 | withdrawVestingTx: fields => createWithdrawVestingTx(fields), 45 | ...payload, 46 | }; 47 | -------------------------------------------------------------------------------- /test/utils/utils.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import { getKeyPair } from 'cryptography'; 3 | import utils from 'utils'; 4 | import chaiHexString from 'test/helpers/chaiHexString'; 5 | 6 | chai.use(chaiHexString); 7 | 8 | describe('#utils', () => { 9 | describe('#randomHash', () => { 10 | it('should generate 16 bytes size hex string', () => { 11 | const randomHexString = utils.randomHex(); 12 | expect(Buffer.byteLength(randomHexString, 'hex')) 13 | .to.be.equal(16); 14 | expect(randomHexString) 15 | .to.be.hexString 16 | .and.not.to.be.equal(utils.randomHex()); 17 | }); 18 | }); 19 | 20 | describe('#isAddress', () => { 21 | describe('Validate public key', () => { 22 | const { pubKey } = getKeyPair(); 23 | it('Address should be hex format', () => { 24 | expect(pubKey).to.be.hexString; 25 | }); 26 | it('It\'s byte length should be 33', () => { 27 | const isValid = utils.isAddress(pubKey); 28 | expect(isValid).to.be.true; 29 | }); 30 | }); 31 | }); 32 | 33 | describe('#padLeftWithZero', () => { 34 | it('should pad when len is longer than string length', () => { 35 | expect(utils.padLeftWithZero('12ab', 10)) 36 | .to.be.equal('00000012ab'); 37 | }); 38 | it('should not pad when len is shorter than string length', () => { 39 | expect(utils.padLeftWithZero('01234', 1)) 40 | .to.be.equal('01234'); 41 | }); 42 | it('should not pad when len is empty', () => { 43 | expect(utils.padLeftWithZero('01234')) 44 | .to.be.equal('01234'); 45 | }); 46 | }); 47 | 48 | describe('#genHexBuf', () => { 49 | it('should generate hex buffer', () => { 50 | expect(utils.genHexBuf('12ab', 5)) 51 | .to.be.eql(Buffer.from('00000012ab', 'hex')); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/local/transaction/utils/constants.js: -------------------------------------------------------------------------------- 1 | const ADD_CERTIFICATION = 'add_certification'; 2 | const BECOME_CANDIDATE = 'become_candidate'; 3 | const DATA_UPLOAD = 'add_record'; 4 | const QUIT_CANDIDATE = 'quit_candidacy'; 5 | const REVOKE_CERTIFICATION = 'revoke_certification'; 6 | const VALUE_TRANSFER = 'transfer'; 7 | const VEST = 'stake'; 8 | const VOTE = 'vote'; 9 | const WITHDRAW_VESTING = 'unstake'; 10 | 11 | // PAYLOAD 12 | const ADD_CERTIFICATION_PAYLOAD = 'addCertificationPayload'; 13 | const ADD_RECORD_PAYLOAD = 'addRecordPayload'; 14 | const DEFAULT_PAYLOAD = 'defaultPayload'; 15 | const REVOKE_CERTIFICATION_PAYLOAD = 'revokeCertificationPayload'; 16 | const VOTE_PAYLOAD = 'votePayload'; 17 | 18 | const BYTESIZES = { 19 | ADDRESS: 33, 20 | ALG: 4, 21 | CHAIN_ID: 4, 22 | NONCE: 8, 23 | VALUE: 16, 24 | HASH: 32, 25 | }; 26 | 27 | const COMMON_REQUIRED = [ 28 | 'chain_id', 29 | 'from', 30 | 'nonce', 31 | 'tx_type', 32 | ]; 33 | 34 | const REQUIRED_TX_PARAMS = { 35 | [ADD_CERTIFICATION]: COMMON_REQUIRED.concat(['payload', 'to']), 36 | [BECOME_CANDIDATE]: COMMON_REQUIRED.concat(['value']), 37 | [DATA_UPLOAD]: COMMON_REQUIRED.concat(['payload']), 38 | [QUIT_CANDIDATE]: COMMON_REQUIRED.concat([]), 39 | [REVOKE_CERTIFICATION]: COMMON_REQUIRED.concat(['payload']), 40 | [VALUE_TRANSFER]: COMMON_REQUIRED.concat(['to', 'value']), 41 | [VEST]: COMMON_REQUIRED.concat(['value']), 42 | [VOTE]: COMMON_REQUIRED.concat(['payload']), 43 | [WITHDRAW_VESTING]: COMMON_REQUIRED.concat(['value']), 44 | }; 45 | 46 | const PAYLOAD_TYPES = { 47 | [ADD_CERTIFICATION]: ADD_CERTIFICATION_PAYLOAD, 48 | [BECOME_CANDIDATE]: null, 49 | [DATA_UPLOAD]: ADD_RECORD_PAYLOAD, 50 | [QUIT_CANDIDATE]: null, 51 | [REVOKE_CERTIFICATION]: REVOKE_CERTIFICATION_PAYLOAD, 52 | [VALUE_TRANSFER]: DEFAULT_PAYLOAD, 53 | [VEST]: null, 54 | [VOTE]: VOTE_PAYLOAD, 55 | [WITHDRAW_VESTING]: null, 56 | }; 57 | 58 | export default { 59 | ADD_CERTIFICATION, 60 | BECOME_CANDIDATE, 61 | DATA_UPLOAD, 62 | QUIT_CANDIDATE, 63 | REVOKE_CERTIFICATION, 64 | VALUE_TRANSFER, 65 | VEST, 66 | VOTE, 67 | WITHDRAW_VESTING, 68 | 69 | BYTESIZES, 70 | 71 | REQUIRED_TX_PARAMS, 72 | PAYLOAD_TYPES, 73 | }; 74 | -------------------------------------------------------------------------------- /src/client/gateway.js: -------------------------------------------------------------------------------- 1 | import { buildConfig } from './config'; 2 | import { MAX_REQUEST_RETRY_COUNT } from './constants'; 3 | import { request } from './httpRequest'; 4 | 5 | export default (nodeBucket) => { 6 | if (!nodeBucket) { 7 | throw new Error('gateway requires bucket for initialization.'); 8 | } 9 | 10 | const runRequest = ({ 11 | config, count, parser, resolve, reject, 12 | }) => { 13 | if (count >= MAX_REQUEST_RETRY_COUNT) { 14 | reject(new Error('max request retry count exceeded.')); 15 | } else { 16 | const baseURL = nodeBucket.getRequestNode(); 17 | request({ ...config, baseURL }) 18 | .then((data) => { 19 | if (typeof parser === 'function') return resolve(parser(data)); 20 | return resolve(data); 21 | }) 22 | .catch((err) => { 23 | if (err.status === 400) { // TODO add more error cases @jiseob 24 | reject(new Error(`${err.data.error} to ${baseURL}`)); 25 | } else { 26 | nodeBucket.replaceRequestNode(); 27 | runRequest({ 28 | config, count: count + 1, resolve, reject, 29 | }); 30 | } 31 | }); 32 | } 33 | }; 34 | 35 | // sendRequest handle request using the nodeBucket. 36 | const sendRequest = ({ 37 | method, parser, path, payload, 38 | }, stream = false) => 39 | new Promise((resolve, reject) => { 40 | // remove blank parameters in the payload 41 | const purePayload = payload; 42 | if (payload) { 43 | const propsName = Object.getOwnPropertyNames(purePayload); 44 | propsName.forEach((name) => { 45 | if (purePayload[name] === null || purePayload[name] === undefined) { 46 | delete purePayload[name]; 47 | } 48 | }); 49 | } 50 | const option = { 51 | method, 52 | path, 53 | payload: purePayload, 54 | }; 55 | if (stream) { 56 | option.responseType = 'stream'; 57 | } 58 | const config = buildConfig(option); 59 | 60 | runRequest({ 61 | config, count: 0, parser, resolve, reject, 62 | }); 63 | }); 64 | 65 | return { 66 | sendRequest, 67 | }; 68 | }; 69 | 70 | -------------------------------------------------------------------------------- /test/client/httpRequest.js: -------------------------------------------------------------------------------- 1 | // import axios from 'axios'; 2 | // import MockAdapter from 'axios-mock-adapter'; 3 | // import chaiAsPromised from 'chai-as-promised'; 4 | // import chai, { expect } from 'chai'; 5 | // // import sinon from 'sinon'; 6 | // import { GET } from 'client/constants'; 7 | // import { request } from 'client/httpRequest'; 8 | // 9 | // chai.use(chaiAsPromised); 10 | // 11 | // describe('httpRequest', () => { 12 | // const defaultConfig = { 13 | // baseURL: 'http://localhost:10000', 14 | // method: GET, 15 | // params: { 16 | // address: 'abcd', 17 | // height: 1, 18 | // }, 19 | // url: '/v1/user/account', 20 | // validateStatus(status) { 21 | // return status >= 200 && status < 500; 22 | // }, 23 | // }; 24 | // const defaultResponseData = { 25 | // balance: 10, 26 | // nonce: 0, 27 | // type: 0, 28 | // }; 29 | // 30 | // describe('#request', () => { 31 | // let mock; 32 | // beforeEach(() => { 33 | // mock = new MockAdapter(axios); 34 | // }); 35 | // 36 | // afterEach(() => { 37 | // mock.reset(); 38 | // }); 39 | // 40 | // after(() => { 41 | // mock.restore(); 42 | // }); 43 | // 44 | // it('should resolves data', async () => { 45 | // mock.onGet(defaultConfig.url).reply(200, { 46 | // ...defaultResponseData, 47 | // }); 48 | // return expect(request(defaultConfig)) 49 | // .to.eventually.eql(defaultResponseData); 50 | // }); 51 | // 52 | // it('should rejects if status is invalid', async () => { 53 | // mock.onGet('/invalid/status').reply(500, {}); 54 | // return expect(request('/invalid/status')) 55 | // .to.be.rejectedWith(Error, 'Request failed with status code 500'); 56 | // }); 57 | // 58 | // it('should rejects if timeout', async () => { 59 | // mock.onGet('/timeout').timeout(); 60 | // return expect(request('/timeout')) 61 | // .to.be.rejectedWith(Error, 'timeout of 0ms exceeded'); 62 | // }); 63 | // 64 | // it('should rejects if network error', async () => { 65 | // mock.onGet('/network/error').networkError(); 66 | // return expect(request('/network/error')) 67 | // .to.be.rejectedWith(Error, 'Network Error'); 68 | // }); 69 | // }); 70 | // }); 71 | -------------------------------------------------------------------------------- /src/local/transaction/payload.js: -------------------------------------------------------------------------------- 1 | import { genHexBuf, recoverFromPayload } from 'utils'; 2 | import { BYTESIZES, PAYLOAD_TYPES } from './utils/constants'; 3 | import * as jsonDescriptor from './utils/proto/transaction.pb.json'; 4 | 5 | // The time unit must be seconds 6 | const createAddCertificationPayload = ({ 7 | issueTime, 8 | expirationTime, 9 | hash, 10 | }) => { 11 | if (!issueTime || !expirationTime || !hash) throw new Error('All parameter should be entered'); 12 | if (issueTime.toString().length !== 10 || expirationTime.toString().length !== 10) throw new Error('Timestamp unit should be seconds'); 13 | if (issueTime > expirationTime) throw new Error('Issuance time should be earlier than expiration time'); 14 | return ({ 15 | issueTime, 16 | expirationTime, 17 | hash: genHexBuf(hash, BYTESIZES.HASH), 18 | }); 19 | }; 20 | 21 | const createDataPayload = hash => ({ 22 | hash: genHexBuf(hash, BYTESIZES.HASH), 23 | }); 24 | 25 | // All parameter type is allowed 26 | const createDefaultPayload = (message) => { 27 | if (typeof message !== 'string') throw new Error('payload type should be a string'); 28 | return { message }; 29 | }; 30 | 31 | const createRevokeCertificationPayload = hash => ({ 32 | hash: genHexBuf(hash, BYTESIZES.HASH), 33 | }); 34 | 35 | const createVotePayload = (candidateIds) => { 36 | const candidates = []; 37 | for (let i = 0; i < candidateIds.length; i += 1) { 38 | candidates.push(genHexBuf(candidateIds[i], BYTESIZES.HASH)); 39 | } 40 | return ({ 41 | candidates, 42 | }); 43 | }; 44 | 45 | const recoverPayload = (transaction) => { 46 | if (!(transaction.rawTx && 47 | transaction.rawTx.tx_type && 48 | transaction.rawTx.payload && 49 | PAYLOAD_TYPES[transaction.rawTx.tx_type])) return null; 50 | return recoverFromPayload( 51 | transaction.rawTx.payload, 52 | PAYLOAD_TYPES[transaction.rawTx.tx_type], 53 | jsonDescriptor, 54 | ); 55 | }; 56 | 57 | const recoverPayloadWithType = (payload, type) => { 58 | if (!(payload && type && PAYLOAD_TYPES[type])) return null; 59 | return recoverFromPayload(payload, PAYLOAD_TYPES[type], jsonDescriptor); 60 | }; 61 | 62 | export default { 63 | createAddCertificationPayload, 64 | createDataPayload, 65 | createDefaultPayload, 66 | createRevokeCertificationPayload, 67 | createVotePayload, 68 | recoverPayload, 69 | recoverPayloadWithType, 70 | }; 71 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | init, 4 | client, 5 | cryptography, 6 | healthData, 7 | identification, 8 | local, 9 | utils, 10 | } from 'index'; 11 | 12 | describe('panacea-js', () => { 13 | const defaultNodes = ['http://localhost:10000']; 14 | 15 | describe('#should export a init', () => { 16 | let panaceajs; 17 | beforeEach(() => { 18 | panaceajs = init(defaultNodes); 19 | return Promise.resolve(); 20 | }); 21 | 22 | it('should be a object', () => { 23 | return expect(panaceajs) 24 | .to.be.an('object'); 25 | }); 26 | 27 | it('should have a client object', () => { 28 | return expect(panaceajs) 29 | .to.be.property('client') 30 | .to.be.a('object'); 31 | }); 32 | 33 | it('should have a cryptography object', () => { 34 | return expect(panaceajs) 35 | .to.be.property('cryptography') 36 | .to.be.an('object'); 37 | }); 38 | 39 | it('should have a healthData object', () => { 40 | return expect(panaceajs) 41 | .to.be.property('healthData') 42 | .to.be.an('object'); 43 | }); 44 | 45 | it('should have a identification object', () => { 46 | return expect(panaceajs) 47 | .to.be.property('identification') 48 | .to.be.an('object'); 49 | }); 50 | 51 | it('should have a local object', () => { 52 | return expect(panaceajs) 53 | .to.be.property('local') 54 | .to.be.an('object'); 55 | }); 56 | 57 | it('should have a utils object', () => { 58 | return expect(panaceajs) 59 | .to.be.property('utils') 60 | .to.be.an('object'); 61 | }); 62 | }); 63 | 64 | it('should export a client object', () => { 65 | return expect(client) 66 | .to.be.an('function'); 67 | }); 68 | 69 | it('should export a cryptography object', () => { 70 | return expect(cryptography) 71 | .to.be.an('object'); 72 | }); 73 | 74 | it('should export a healthData object', () => { 75 | return expect(healthData) 76 | .to.be.an('object'); 77 | }); 78 | 79 | it('should export a identification object', () => { 80 | return expect(identification) 81 | .to.be.an('object'); 82 | }); 83 | 84 | it('should export a local object', () => { 85 | return expect(local) 86 | .to.be.an('object'); 87 | }); 88 | 89 | it('should export a utils object', () => { 90 | return expect(utils) 91 | .to.be.an('object'); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@medibloc/panacea-js", 3 | "version": "1.0.2", 4 | "description": "JavaScript library for MediBloc", 5 | "main": "lib/index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "babel": "babel src -d lib --copy-files", 11 | "browserify": "browserify ./lib/index.js -o ./dist/panaceajs.js -s panaceajs", 12 | "build": "npm run clean && npm run babel && npm run browserify && npm run uglify", 13 | "clean": "npm run clean-dist && npm run clean-lib", 14 | "clean-dist": "rm -rf dist", 15 | "clean-lib": "rm -rf lib", 16 | "lint": "eslint .", 17 | "test": "mocha --recursive --require babel-core/register --reporter spec", 18 | "uglify": "uglifyjs -nm -o ./dist/panaceajs.min.js ./dist/panaceajs.js" 19 | }, 20 | "pre-commit": [ 21 | "lint", 22 | "test" 23 | ], 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/medibloc/panacea-js.git" 27 | }, 28 | "keywords": [ 29 | "medibloc", 30 | "javascript", 31 | "library", 32 | "panacea" 33 | ], 34 | "author": "MediBloc ", 35 | "license": "Apache-2.0", 36 | "bugs": { 37 | "url": "https://github.com/medibloc/panacea-js/issues" 38 | }, 39 | "homepage": "https://github.com/medibloc/panacea-js#readme", 40 | "dependencies": { 41 | "axios": "^0.18.0", 42 | "babel-runtime": "^6.26.0", 43 | "bignumber.js": "^7.0.1", 44 | "bops": "^1.0.0", 45 | "browserify": "^16.2.3", 46 | "js-sha3": "^0.7.0", 47 | "jsonfile": "^4.0.0", 48 | "protobufjs": "~6.8.6", 49 | "scrypt.js": "^0.2.0", 50 | "secp256k1": "^3.5.0", 51 | "underscore": "^1.9.1", 52 | "unorm": "^1.4.1", 53 | "uuid": "^3.2.1" 54 | }, 55 | "devDependencies": { 56 | "axios-mock-adapter": "^1.15.0", 57 | "babel-cli": "^6.26.0", 58 | "babel-plugin-add-module-exports": "^1.0.2", 59 | "babel-plugin-module-resolver": "^3.2.0", 60 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 61 | "babel-plugin-transform-runtime": "^6.23.0", 62 | "babel-preset-es2015": "^6.24.1", 63 | "chai": "^4.1.2", 64 | "chai-as-promised": "^7.1.1", 65 | "eslint": "^4.19.1", 66 | "eslint-config-airbnb-base": "^12.1.0", 67 | "eslint-import-resolver-babel-module": "^4.0.0", 68 | "eslint-plugin-import": "^2.14.0", 69 | "mocha": "^6.1.4", 70 | "pre-commit": "^1.2.2", 71 | "proxyquire": "^2.0.1", 72 | "sinon": "^6.3.5", 73 | "sinon-chai": "^3.3.0", 74 | "uglify-es": "^3.3.9" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/cryptography/keyGen.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import cryptography from 'cryptography'; 3 | import chaiHexString from 'test/helpers/chaiHexString'; 4 | 5 | chai.use(chaiHexString); 6 | 7 | describe('#keyGen', () => { 8 | let keyPair; 9 | let keyPairFP; // keyPairFromPassphrase 10 | const passphrase = 'a b c d e f g h i j k l'; 11 | beforeEach(() => { 12 | keyPair = cryptography.getKeyPair(); 13 | keyPairFP = cryptography.getKeyPairFromPassphrase(passphrase); 14 | return Promise.resolve(); 15 | }); 16 | 17 | describe('#getKeyPair', () => { 18 | it('should return 32 bytes private key in hex format', () => { 19 | expect(keyPair) 20 | .to.have.property('privKey') 21 | .and.to.be.hexString; 22 | expect(Buffer.byteLength(keyPair.privKey, 'hex')) 23 | .to.be.equal(32); 24 | }); 25 | 26 | it('should return 33 bytes public key in hex format', () => { 27 | expect(keyPair) 28 | .to.have.property('pubKey') 29 | .and.to.be.hexString; 30 | expect(Buffer.byteLength(keyPair.pubKey, 'hex')) 31 | .to.be.equal(33); 32 | }); 33 | }); 34 | 35 | describe('#getKeyPairFromPassphrase', () => { 36 | it('should return 32 bytes private key in hex format', () => { 37 | expect(keyPairFP) 38 | .to.have.property('privKey') 39 | .and.to.be.hexString 40 | .to.equal('55a04e059fe69b780cccf5f80564d8ec4c11806475dc41b237f3a566b27a69c1'); 41 | expect(Buffer.byteLength(keyPairFP.privKey, 'hex')) 42 | .to.be.equal(32); 43 | }); 44 | 45 | it('should return 33 bytes public key in hex format', () => { 46 | expect(keyPairFP) 47 | .to.have.property('pubKey') 48 | .and.to.hexString 49 | .to.equal('03d71df469d681c9bc89ef0c0b94c5af530f3a1fa69d15d9bc79948a198c43ff00'); 50 | expect(Buffer.byteLength(keyPairFP.pubKey, 'hex')) 51 | .to.be.equal(33); 52 | }); 53 | }); 54 | 55 | describe('#getPubKey', () => { 56 | it('should be matched with the public key', () => { 57 | expect(keyPair.pubKey) 58 | .to.be.equal(cryptography.getPubKey(keyPair.privKey)); 59 | expect(keyPairFP.pubKey) 60 | .to.be.equal(cryptography.getPubKey(keyPairFP.privKey)); 61 | }); 62 | }); 63 | 64 | describe('#getSharedSecretKey', () => { 65 | it('should same with each other', () => { 66 | const secondKeyPair = cryptography.getKeyPair(); 67 | expect(cryptography.getSharedSecretKey(keyPair.privKey, secondKeyPair.pubKey)) 68 | .to.be.equal(cryptography.getSharedSecretKey(secondKeyPair.privKey, keyPair.pubKey)); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /src/local/transaction/utils/proto/transaction.pb.json: -------------------------------------------------------------------------------- 1 | { 2 | "nested": { 3 | "corepb": { 4 | "nested": { 5 | "TransactionHashTarget": { 6 | "fields": { 7 | "txType": { 8 | "type": "string", 9 | "id": 1 10 | }, 11 | "from": { 12 | "type": "bytes", 13 | "id": 2 14 | }, 15 | "to": { 16 | "type": "bytes", 17 | "id": 3 18 | }, 19 | "value": { 20 | "type": "bytes", 21 | "id": 4 22 | }, 23 | "nonce": { 24 | "type": "uint64", 25 | "id": 5 26 | }, 27 | "chainId": { 28 | "type": "uint32", 29 | "id": 6 30 | }, 31 | "payload": { 32 | "type": "bytes", 33 | "id": 10 34 | } 35 | } 36 | }, 37 | "TransactionPayerSignTarget": { 38 | "fields": { 39 | "hash": { 40 | "type": "bytes", 41 | "id": 1 42 | }, 43 | "sign": { 44 | "type": "bytes", 45 | "id": 2 46 | } 47 | } 48 | }, 49 | "DefaultPayload": { 50 | "fields": { 51 | "message": { 52 | "type": "string", 53 | "id": 1 54 | } 55 | } 56 | }, 57 | "VotePayload": { 58 | "fields": { 59 | "candidates": { 60 | "rule": "repeated", 61 | "type": "bytes", 62 | "id": 1 63 | } 64 | } 65 | }, 66 | "AddCertificationPayload": { 67 | "fields": { 68 | "issueTime": { 69 | "type": "int64", 70 | "id": 1 71 | }, 72 | "expirationTime": { 73 | "type": "int64", 74 | "id": 2 75 | }, 76 | "hash": { 77 | "type": "bytes", 78 | "id": 3 79 | } 80 | } 81 | }, 82 | "RevokeCertificationPayload": { 83 | "fields": { 84 | "hash": { 85 | "type": "bytes", 86 | "id": 1 87 | } 88 | } 89 | }, 90 | "AddRecordPayload": { 91 | "fields": { 92 | "hash": { 93 | "type": "bytes", 94 | "id": 1 95 | } 96 | } 97 | } 98 | } 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /test/client/config.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | APPLICATION_JSON, 4 | NETWORK_TIMEOUT, 5 | GET, 6 | POST, 7 | } from 'client/constants'; 8 | import config from 'client/config'; 9 | 10 | describe('config', () => { 11 | const defaultGetConfig = { 12 | baseURL: 'http://localhost:10000', 13 | method: GET, 14 | path: '/v1/path/of/api/get', 15 | payload: { 16 | firstParam: 1, 17 | secondParam: 2, 18 | }, 19 | }; 20 | 21 | const defaultPostConfig = { 22 | baseURL: 'http://localhost:10000', 23 | method: POST, 24 | path: '/v1/path/of/api/post', 25 | payload: { 26 | firstParam: 1, 27 | secondParam: 2, 28 | }, 29 | }; 30 | 31 | describe('#buildConfig', () => { 32 | describe('returned object', () => { 33 | const getConfig = config.buildConfig(defaultGetConfig); 34 | 35 | it('should be a object', () => { 36 | return expect(getConfig) 37 | .to.be.an('object'); 38 | }); 39 | 40 | it('should have a timeout field as default', () => { 41 | return expect(getConfig) 42 | .to.have.property('timeout') 43 | .and.equal(NETWORK_TIMEOUT); 44 | }); 45 | 46 | it('should have the correct baseURL field', () => { 47 | return expect(getConfig) 48 | .to.have.property('baseURL') 49 | .and.equal(defaultGetConfig.baseURL); 50 | }); 51 | 52 | it('should have the correct method field', () => { 53 | return expect(getConfig) 54 | .to.have.property('method') 55 | .and.equal(defaultGetConfig.method); 56 | }); 57 | 58 | it('should have the correct url field', () => { 59 | return expect(getConfig) 60 | .to.have.property('url') 61 | .and.equal(defaultGetConfig.path); 62 | }); 63 | }); 64 | 65 | describe('get method type', () => { 66 | const getConfig = config.buildConfig(defaultGetConfig); 67 | 68 | it('should have the correct params object', () => { 69 | return expect(getConfig) 70 | .to.have.property('params') 71 | .and.to.be.a('object') 72 | .and.eql(defaultGetConfig.payload); 73 | }); 74 | }); 75 | 76 | describe('post method type', () => { 77 | const postConfig = config.buildConfig(defaultPostConfig); 78 | 79 | it('should have the correct data object', () => { 80 | return expect(postConfig) 81 | .to.have.property('data') 82 | .and.to.be.an('object') 83 | .and.eql(defaultPostConfig.payload); 84 | }); 85 | 86 | it('should have the correct headers object', () => { 87 | return expect(postConfig) 88 | .to.have.property('headers') 89 | .and.to.be.an('object') 90 | .and.eql(APPLICATION_JSON); 91 | }); 92 | }); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /src/healthData/encoder.js: -------------------------------------------------------------------------------- 1 | import { sha3 } from 'utils'; 2 | import { BYTESIZES, OFFSETS, TYPE_REV, SUBTYPE_REV } from './constants'; 3 | import Helper from './helper'; 4 | import Reader from './reader'; 5 | import mhd from './mhd'; 6 | 7 | const decodeData = data => 8 | new Promise((resolve, reject) => { 9 | if (!(data instanceof Buffer || data instanceof Uint8Array)) { 10 | reject(new Error('not supporting data type')); 11 | } 12 | // TODO: check magic number 13 | // TODO: check protocol version 14 | // parsing type 15 | const type = parseInt(data.slice( 16 | OFFSETS.TYPE, 17 | OFFSETS.TYPE + BYTESIZES.TYPE, 18 | ).toString('hex'), 16); 19 | // parsing subType 20 | const subType = parseInt(data.slice( 21 | OFFSETS.SUBTYPE, 22 | OFFSETS.SUBTYPE + BYTESIZES.SUBTYPE, 23 | ).toString('hex'), 16); 24 | // TODO: parsing data hash 25 | // TODO: check data hash 26 | // parsing data size 27 | const dataSize = parseInt(data.slice( 28 | OFFSETS.DATASIZE, 29 | OFFSETS.DATASIZE + BYTESIZES.DATASIZE, 30 | ).toString('hex'), 16); 31 | // TODO: check data size 32 | const buffer = data.slice( 33 | OFFSETS.DATA, 34 | OFFSETS.DATA + dataSize, 35 | ); 36 | 37 | switch (TYPE_REV[type]) { 38 | case 'medical-fhir': 39 | Helper.decodeFHIR(buffer, SUBTYPE_REV[type][subType]) 40 | .then(obj => resolve(obj)) 41 | .catch(err => reject(err)); 42 | break; 43 | case 'pghd': 44 | if (buffer instanceof Uint8Array || buffer instanceof Buffer) { 45 | resolve(Helper.decodeTxt(buffer)); 46 | } else if (typeof buffer === 'object') { 47 | resolve(Helper.decodeJson(buffer)); 48 | } 49 | reject(new Error('not supporting type')); 50 | break; 51 | default: 52 | reject(new Error('not supporting type')); 53 | } 54 | }); 55 | 56 | const decodeDataFromFile = filePath => 57 | new Promise((resolve, reject) => { 58 | // TODO: read file as Buffer or Uint8Array 59 | Reader.readData(filePath) 60 | .then(data => decodeData(data)) 61 | .catch(err => reject(err)); 62 | }); 63 | 64 | const encodeData = (data, type, subType) => 65 | new Promise((resolve, reject) => { 66 | Helper.makeBuffer(data, type, subType) 67 | .then(dataBuffer => resolve(mhd.makeMHD({ 68 | type, 69 | subType, 70 | hash: sha3(dataBuffer), 71 | size: dataBuffer.length, 72 | dataBuffer, 73 | }))) 74 | .catch(err => reject(err)); 75 | }); 76 | 77 | const encodeDataFromFile = (filePath, type, subType) => 78 | new Promise((resolve, reject) => { 79 | Reader.readData(filePath) 80 | .then(data => resolve(encodeData(data, type, subType))) 81 | .catch(err => reject(err)); 82 | }); 83 | 84 | export default { 85 | decodeData, 86 | decodeDataFromFile, 87 | encodeData, 88 | encodeDataFromFile, 89 | }; 90 | -------------------------------------------------------------------------------- /src/healthData/helper.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import protobuf from 'protobufjs'; 3 | 4 | const FHIRResources = [['patient', 'PatientData'], ['observation', 'Observation'], ['careplan', 'CarePlan']]; 5 | const FHIRResourcesMap = new Map(FHIRResources); 6 | 7 | const decodeFHIR = (buffer, type) => protobuf.load(path.resolve(__dirname, `./proto/json/${type}.json`)) 8 | .then((root) => { 9 | const Type = root.lookupType(FHIRResourcesMap.get(type)); 10 | 11 | // Decode an Uint8Array (browser) or Buffer (node) to a message 12 | const message = Type.decode(buffer); 13 | 14 | const errMsg = Type.verify(message); 15 | if (errMsg) { throw Error(errMsg); } 16 | 17 | // Maybe convert the message back to a plain object 18 | return Type.toObject(message, { 19 | longs: String, 20 | enums: String, 21 | bytes: String, 22 | // see ConversionOptions 23 | }); 24 | }) 25 | .catch((err) => { 26 | console.log(err); 27 | throw err; 28 | }); 29 | 30 | const decodeJson = buffer => JSON.parse(buffer.toString()); 31 | 32 | const decodeTxt = buffer => buffer.toString(); 33 | 34 | const encodeFHIR = (obj, type) => protobuf.load(path.resolve(__dirname, `./proto/json/${type}.json`)) 35 | .then((root) => { 36 | const Type = root.lookupType(FHIRResourcesMap.get(type)); 37 | const errMsg = Type.verify(obj); 38 | if (errMsg) { 39 | throw new Error(errMsg); 40 | } 41 | 42 | // Create a new message 43 | const message = Type.create(obj); // or use .fromObject if conversion is necessary 44 | 45 | // Encode a message to an Uint8Array (browser) or Buffer (node) 46 | return Type.encode(message).finish(); 47 | }).catch((err) => { 48 | console.log(err); 49 | throw err; 50 | }); 51 | 52 | const encodeJson = obj => Buffer.from(JSON.stringify(obj)); 53 | 54 | const encodeString = str => Buffer.from(str); 55 | 56 | const makeBuffer = (data, type, subType) => 57 | new Promise((resolve, reject) => { 58 | // TODO: support other types 59 | switch (type) { 60 | case 'medical-fhir': 61 | encodeFHIR(data, subType) 62 | .then(dataBuffer => resolve(dataBuffer)) 63 | .catch(err => reject(err)); 64 | break; 65 | case 'pghd': 66 | if (data instanceof Uint8Array || data instanceof Buffer) { 67 | resolve(data); 68 | } else if (typeof data === 'object') { 69 | resolve(encodeJson(data)); 70 | } else if (typeof data === 'string') { 71 | resolve(encodeString(data)); 72 | } else { 73 | reject(new Error('not supporting type')); 74 | } 75 | break; 76 | case 'dicom': 77 | resolve(data); 78 | break; 79 | default: 80 | reject(new Error('not supporting type')); 81 | } 82 | }); 83 | 84 | export default { 85 | decodeFHIR, 86 | decodeJson, 87 | decodeTxt, 88 | encodeFHIR, 89 | encodeJson, 90 | encodeString, 91 | makeBuffer, 92 | }; 93 | -------------------------------------------------------------------------------- /src/healthData/proto/stu3/annotations.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // This file defines extensions to FieldOptions and MessageOptions to enable 16 | // annotation proto fields and messages with their corresponding FHIR types. 17 | 18 | syntax = "proto3"; 19 | 20 | package google.fhir.stu3.proto; 21 | option java_outer_classname = "Annotations"; 22 | option java_package = "com.google.fhir.stu3.proto"; 23 | 24 | import "google/protobuf/descriptor.proto"; 25 | 26 | // TODO(sundberg): Unify with StructureDefinitionKindCode 27 | enum StructureDefinitionKindValue { 28 | KIND_UNKNOWN = 0; 29 | KIND_PRIMITIVE_TYPE = 1; 30 | KIND_COMPLEX_TYPE = 2; 31 | KIND_RESOURCE = 3; 32 | KIND_LOGICAL = 4; 33 | } 34 | 35 | // To annotate cardinality constraints. 36 | enum Requirement { 37 | NOT_REQUIRED = 0; 38 | REQUIRED_BY_FHIR = 1; 39 | } 40 | 41 | extend google.protobuf.MessageOptions { 42 | // If this message is the proto version of a fhir extension, this is the 43 | // extension identifier. 44 | string fhir_extension_url = 177048773; 45 | 46 | // If this message is a Code constrained to a specific valueset, this is the 47 | // valueset identifier. 48 | string fhir_valueset_url = 180887441; 49 | 50 | // If this message is a Reference, the reference is constrained to these 51 | // resource types. 52 | repeated string fhir_reference_type = 183546385; 53 | 54 | // What type of fhir structure does this message represent? 55 | StructureDefinitionKindValue structure_definition_kind = 182131192; 56 | 57 | // A brief description of the intended use of this message. 58 | string message_description = 190563156; 59 | } 60 | 61 | extend google.protobuf.EnumValueOptions { 62 | // If we had to rename this code to make a valid enum identifier, what was 63 | // the original name? 64 | string fhir_original_code = 181000551; 65 | } 66 | 67 | extend google.protobuf.FieldOptions { 68 | // Is this field required? 69 | Requirement validation_requirement = 162282766; 70 | 71 | // Is this field a wrapper around a choice type? 72 | bool is_choice_type = 187112991; 73 | 74 | // A brief description of the intended use of this field. 75 | string field_description = 190357977; 76 | } 77 | 78 | extend google.protobuf.OneofOptions { 79 | // Is this field required? 80 | Requirement oneof_validation_requirement = 181563459; 81 | } 82 | -------------------------------------------------------------------------------- /test/client/nodeBucket.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import nodeBucket from 'client/nodeBucket'; 3 | 4 | describe('nodeBucket', () => { 5 | const defaultNodes = ['http://localhost:10000', 'http://localhost:10001']; 6 | let newNodeBucket; 7 | let expectedNodes; 8 | beforeEach(() => { 9 | expectedNodes = defaultNodes.slice(); 10 | newNodeBucket = nodeBucket(defaultNodes.slice()); 11 | return Promise.resolve(); 12 | }); 13 | 14 | describe('#returned object', () => { 15 | it('should be a object', () => { 16 | return expect(newNodeBucket) 17 | .to.be.an('object'); 18 | }); 19 | 20 | it('should have a getNodes function', () => { 21 | return expect(newNodeBucket) 22 | .to.be.property('getNodes') 23 | .to.be.an('function'); 24 | }); 25 | 26 | it('should have a getSize function', () => { 27 | return expect(newNodeBucket) 28 | .to.be.property('getSize') 29 | .to.be.an('function'); 30 | }); 31 | 32 | it('should have a getRequestNode function', () => { 33 | return expect(newNodeBucket) 34 | .to.be.property('getRequestNode') 35 | .to.be.an('function'); 36 | }); 37 | 38 | it('should have a replaceRequestNode function', () => { 39 | return expect(newNodeBucket) 40 | .to.be.property('replaceRequestNode') 41 | .to.be.an('function'); 42 | }); 43 | 44 | it('should throw an error if nodes argument is empty', () => { 45 | return expect(() => nodeBucket()) 46 | .to.throw(Error, 'nodeBucket requires array of nodes for initialization.'); 47 | }); 48 | 49 | it('should throw an error if nodes argument is not array', () => { 50 | return expect(() => nodeBucket('http://localhost:10000')) 51 | .to.throw(Error, 'nodeBucket requires array of nodes for initialization.'); 52 | }); 53 | 54 | it('should throw an error if nodes argument is empty array', () => { 55 | return expect(() => nodeBucket([])) 56 | .to.throw(Error, 'nodeBucket requires array of nodes for initialization.'); 57 | }); 58 | }); 59 | 60 | describe('#getNodes', () => { 61 | it('should return the nodes', () => { 62 | return expect(newNodeBucket.getNodes()) 63 | .to.be.eql(expectedNodes); 64 | }); 65 | }); 66 | 67 | describe('#getSize', () => { 68 | it('should return the nodes count', () => { 69 | return expect(newNodeBucket.getSize()) 70 | .to.be.eql(expectedNodes.length); 71 | }); 72 | }); 73 | 74 | describe('#getRequestNode', () => { 75 | it('should return the request node', () => { 76 | return expect(newNodeBucket.getRequestNode()) 77 | .to.be.eql(expectedNodes[0]); 78 | }); 79 | }); 80 | 81 | describe('#replaceRequestNode', () => { 82 | it('should replace the request node', () => { 83 | newNodeBucket.replaceRequestNode(); 84 | expect(newNodeBucket.getRequestNode()) 85 | .to.be.eql(expectedNodes[1]); 86 | newNodeBucket.replaceRequestNode(); 87 | return expect(newNodeBucket.getRequestNode()) 88 | .to.be.eql(expectedNodes[0]); 89 | }); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /src/local/account/account.js: -------------------------------------------------------------------------------- 1 | import { sha3_256 as SHA3256 } from 'js-sha3'; 2 | import { 3 | decryptKey, 4 | encryptKey, 5 | getKeyPair, 6 | getPubKey, 7 | sign, 8 | } from 'cryptography'; 9 | import { createCertificate } from 'identification'; 10 | import protobuf from 'protobufjs/light'; 11 | import { isAddress, genHexBuf } from 'utils'; 12 | import * as jsonDescriptor from '../transaction/utils/proto/transaction.pb.json'; 13 | import { BYTESIZES } from '../../healthData/constants'; 14 | 15 | // generate new keypair and register 16 | const generateAccount = (passphrase = '') => { 17 | const { privKey, pubKey } = getKeyPair(); 18 | 19 | return { 20 | encryptedPrivKey: encryptKey(passphrase, privKey), 21 | pubKey, 22 | }; 23 | }; 24 | 25 | // set encrypted private key 26 | const setEncryptedPrivateKey = (passphrase = '', encryptedPrivKey, pubKey) => ({ 27 | encryptedPrivKey, 28 | pubKey: isAddress(pubKey) ? pubKey : getPubKey(decryptKey(passphrase, encryptedPrivKey)), 29 | }); 30 | 31 | export default class Account { 32 | constructor(passphrase, encryptedPrivKey = '', pubKey) { 33 | let newAccount; 34 | if (encryptedPrivKey === '') { 35 | newAccount = generateAccount(passphrase); 36 | } else { 37 | newAccount = setEncryptedPrivateKey(passphrase, encryptedPrivKey, pubKey); 38 | } 39 | Object.keys(newAccount).forEach((key) => { 40 | this[key] = newAccount[key]; 41 | }); 42 | } 43 | 44 | // get decrypted private key 45 | getDecryptedPrivateKey(passphrase = '') { 46 | return decryptKey(passphrase, this.encryptedPrivKey); 47 | } 48 | 49 | signTx(tx, passphrase = '') { 50 | const privKey = this.getDecryptedPrivateKey(passphrase); 51 | tx.sign = sign(privKey, tx.hash); // eslint-disable-line 52 | } 53 | 54 | signTxAsPayer(tx, passphrase = '') { 55 | if (tx.hash.length === BYTESIZES.HASH || tx.sign.length === 0) { 56 | throw new Error('Valid transaction hash and signature are required'); 57 | } 58 | const privKey = this.getDecryptedPrivateKey(passphrase); 59 | 60 | const txPayerSignTarget = { 61 | hash: genHexBuf(tx.hash, BYTESIZES.HASH), 62 | sign: Buffer.from(tx.sign, 'hex'), 63 | }; 64 | const root = protobuf.Root.fromJSON(jsonDescriptor); 65 | const TxPayerSignTarget = root.lookupType('TransactionPayerSignTarget'); 66 | const errMsg = TxPayerSignTarget.verify(txPayerSignTarget); 67 | if (errMsg) throw Error(errMsg); 68 | const message = TxPayerSignTarget.create(txPayerSignTarget); 69 | const buf = TxPayerSignTarget.encode(message).finish(); 70 | 71 | // eslint-disable-next-line no-param-reassign 72 | tx.payerSign = sign(privKey, SHA3256.create().update(buf).hex()); 73 | } 74 | 75 | signDataPayload(data, passphrase = '') { 76 | const privKey = this.getDecryptedPrivateKey(passphrase); 77 | data.sign = sign(privKey, data.hash); // eslint-disable-line 78 | data.cert = this.cert; // eslint-disable-line 79 | } 80 | 81 | createCertificate({ 82 | expireDate, 83 | issuer, 84 | issueDate, 85 | passphrase = '', 86 | }) { 87 | this.cert = createCertificate({ 88 | expireDate, 89 | issuer, 90 | issuerAccount: this, 91 | issueDate, 92 | passphrase, 93 | pubKey: this.pubKey, 94 | }); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /test/local/transaction/utils/checkTx.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { Account } from 'local/account'; 3 | import { checkTx, constants, setTx } from 'local/transaction/utils'; 4 | 5 | const { checkObject, checkRequiredParams, checkValue } = checkTx; 6 | const { 7 | DATA_UPLOAD, 8 | VALUE_TRANSFER, 9 | REQUIRED_TX_PARAMS, 10 | } = constants; 11 | 12 | // checkTx 13 | describe('# checkTx function', () => { 14 | const user = new Account(''); 15 | const dataUploadTxData = { 16 | from: user.pubKey, 17 | nonce: 3, 18 | type: DATA_UPLOAD, 19 | payload: { 20 | Hash: 'hash', 21 | }, 22 | }; 23 | const dataUploadTx = setTx(dataUploadTxData); 24 | 25 | const valueTransferTxData = { 26 | from: user.pubKey, 27 | to: user.pubKey, 28 | value: '5', 29 | nonce: 3, 30 | type: VALUE_TRANSFER, 31 | }; 32 | const valueTransferTx = setTx(valueTransferTxData); 33 | 34 | it('Throw error unless tx input is object', () => { 35 | const stringInput = JSON.stringify(valueTransferTx); 36 | expect(() => checkObject(stringInput)).to.throw(Error, 'Transaction format should be object.'); 37 | const numberInput = 123; 38 | expect(() => checkObject(numberInput)).to.throw(Error, 'Transaction format should be object.'); 39 | }); 40 | 41 | it('Throw error if transaction doesn\'t have required params 1', () => { 42 | REQUIRED_TX_PARAMS[VALUE_TRANSFER].forEach((param) => { 43 | let tempTx; 44 | const p = param.split('.'); 45 | if (p.length === 1) { 46 | tempTx = Object.assign( 47 | {}, 48 | valueTransferTx, 49 | { 50 | [p[0]]: undefined, 51 | }, 52 | ); 53 | } else if (p.length === 2) { 54 | tempTx = Object.assign( 55 | {}, 56 | valueTransferTx, 57 | { 58 | [p[0]]: { 59 | [p[1]]: undefined, 60 | }, 61 | }, 62 | ); 63 | } 64 | expect(() => checkRequiredParams(tempTx, REQUIRED_TX_PARAMS[VALUE_TRANSFER])).to.throw(Error, `Transaction should have ${param} field.`); 65 | }); 66 | }); 67 | 68 | it('Throw error if transaction doesn\'t have required params 2', () => { 69 | REQUIRED_TX_PARAMS[DATA_UPLOAD].forEach((param) => { 70 | let tempTx; 71 | const p = param.split('.'); 72 | if (p.length === 1) { 73 | tempTx = Object.assign( 74 | {}, 75 | dataUploadTx, 76 | { 77 | [param]: undefined, 78 | }, 79 | ); 80 | } else if (p.length === 2) { 81 | tempTx = Object.assign( 82 | {}, 83 | dataUploadTx, 84 | { 85 | [p[0]]: Object.assign( 86 | {}, 87 | dataUploadTx[p[0]], 88 | { 89 | [p[1]]: undefined, 90 | }, 91 | ), 92 | }, 93 | ); 94 | } 95 | expect(() => checkRequiredParams(tempTx, REQUIRED_TX_PARAMS[DATA_UPLOAD])).to.throw(Error, `Transaction should have ${param} field.`); 96 | }); 97 | }); 98 | 99 | it('Throw error if value type isnt a string or value exceeds the max or negative', () => { 100 | // MAX : 340282366920938463463374607431768211455 101 | valueTransferTxData.value = 7922816251426; 102 | const wrongTypeValueTx = setTx(valueTransferTxData); 103 | expect(() => checkValue(wrongTypeValueTx)).to.throw(Error, 'Type of value need to be string'); 104 | valueTransferTxData.value = '340282366920938463463374607431768211456'; 105 | const overValueTx = setTx(valueTransferTxData); 106 | expect(() => checkValue(overValueTx)).to.throw(Error, 'Amount is too large'); 107 | valueTransferTxData.value = '-100'; 108 | const negativeValueTx = setTx(valueTransferTxData); 109 | expect(() => checkValue(negativeValueTx)).to.throw(Error, 'Can not send negative value'); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /test/healthData/samples/patient_sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "Patient", 3 | "id": "example", 4 | "text": { 5 | "status": "generated", 6 | "div": "
\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
NamePeter James \n Chalmers ("Jim")\n
Address534 Erewhon, Pleasantville, Vic, 3999
ContactsHome: unknown. Work: (03) 5555 6473
IdMRN: 12345 (Acme Healthcare)
\n\t\t
" 7 | }, 8 | "identifier": [ 9 | { 10 | "use": "usual", 11 | "type": { 12 | "coding": [ 13 | { 14 | "system": "http://hl7.org/fhir/v2/0203", 15 | "code": "MR" 16 | } 17 | ] 18 | }, 19 | "system": "urn:oid:1.2.36.146.595.217.0.1", 20 | "value": "12345", 21 | "period": { 22 | "start": "2001-05-06" 23 | }, 24 | "assigner": { 25 | "display": "Acme Healthcare" 26 | } 27 | } 28 | ], 29 | "active": true, 30 | "name": [ 31 | { 32 | "use": "official", 33 | "family": "Chalmers", 34 | "given": [ 35 | "Peter", 36 | "James" 37 | ] 38 | }, 39 | { 40 | "use": "usual", 41 | "given": [ 42 | "Jim" 43 | ] 44 | }, 45 | { 46 | "use": "maiden", 47 | "family": "Windsor", 48 | "given": [ 49 | "Peter", 50 | "James" 51 | ], 52 | "period": { 53 | "end": "2002" 54 | } 55 | } 56 | ], 57 | "telecom": [ 58 | { 59 | "use": "home" 60 | }, 61 | { 62 | "system": "phone", 63 | "value": "(03) 5555 6473", 64 | "use": "work", 65 | "rank": 1 66 | }, 67 | { 68 | "system": "phone", 69 | "value": "(03) 3410 5613", 70 | "use": "mobile", 71 | "rank": 2 72 | }, 73 | { 74 | "system": "phone", 75 | "value": "(03) 5555 8834", 76 | "use": "old", 77 | "period": { 78 | "end": "2014" 79 | } 80 | } 81 | ], 82 | "gender": "male", 83 | "birthDate": "1974-12-25", 84 | "deceasedBoolean": false, 85 | "address": [ 86 | { 87 | "use": "home", 88 | "type": "both", 89 | "text": "534 Erewhon St PeasantVille, Rainbow, Vic 3999", 90 | "line": [ 91 | "534 Erewhon St" 92 | ], 93 | "city": "PleasantVille", 94 | "district": "Rainbow", 95 | "state": "Vic", 96 | "postalCode": "3999", 97 | "period": { 98 | "start": "1974-12-25" 99 | } 100 | } 101 | ], 102 | "contact": [ 103 | { 104 | "relationship": [ 105 | { 106 | "coding": [ 107 | { 108 | "system": "http://hl7.org/fhir/v2/0131", 109 | "code": "N" 110 | } 111 | ] 112 | } 113 | ], 114 | "name": { 115 | "family": "du Marché", 116 | "given": [ 117 | "Bénédicte" 118 | ] 119 | }, 120 | "telecom": [ 121 | { 122 | "system": "phone", 123 | "value": "+33 (237) 998327" 124 | } 125 | ], 126 | "address": { 127 | "use": "home", 128 | "type": "both", 129 | "line": [ 130 | "534 Erewhon St" 131 | ], 132 | "city": "PleasantVille", 133 | "district": "Rainbow", 134 | "state": "Vic", 135 | "postalCode": "3999", 136 | "period": { 137 | "start": "1974-12-25" 138 | } 139 | }, 140 | "gender": "female", 141 | "period": { 142 | "start": "2012" 143 | } 144 | } 145 | ], 146 | "managingOrganization": { 147 | "reference": "Organization/1" 148 | } 149 | } -------------------------------------------------------------------------------- /test/local/transaction/payload.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { 3 | createAddCertificationPayload, 4 | createDataPayload, 5 | createDefaultPayload, 6 | createRevokeCertificationPayload, 7 | createVotePayload, 8 | recoverPayloadWithType, 9 | } from 'local/transaction/payload'; 10 | import { 11 | ADD_CERTIFICATION, 12 | DATA_UPLOAD, 13 | PAYLOAD_TYPES, 14 | REVOKE_CERTIFICATION, 15 | VALUE_TRANSFER, 16 | VOTE, 17 | } from 'local/transaction/utils/constants'; 18 | import protobuf from 'protobufjs/light'; 19 | import * as jsonDescriptor from 'local/transaction/utils/proto/transaction.pb.json'; 20 | 21 | 22 | const genPayloadPb = (payload, type) => { 23 | const root = protobuf.Root.fromJSON(jsonDescriptor); 24 | const PayloadTarget = root.lookupType(type); 25 | const message = PayloadTarget.create(payload); 26 | return PayloadTarget.encode(message).finish().toString('hex'); 27 | }; 28 | 29 | describe('# payload', () => { 30 | describe('# createAddCertificationPayload', () => { 31 | it('should be matched with the expected result', () => { 32 | const type = PAYLOAD_TYPES[ADD_CERTIFICATION].charAt(0).toUpperCase() + 33 | PAYLOAD_TYPES[ADD_CERTIFICATION].slice(1); 34 | const payload = createAddCertificationPayload({ 35 | issueTime: 1535002808, 36 | expirationTime: 1566538808, 37 | hash: '9eca7128409f609b2a72fc24985645665bbb99152b4b14261c3c3c93fb17cf54', 38 | }); 39 | const target = '08b891f9db0510b8f8fdea051a209eca7128409f609b2a72fc24985645665bbb99152b4b14261c3c3c93fb17cf54'; 40 | expect(genPayloadPb(payload, type)).to.be.eql(target); 41 | expect(recoverPayloadWithType(target, ADD_CERTIFICATION)).to.be.eql(payload); 42 | }); 43 | }); 44 | 45 | describe('# createDataPayload', () => { 46 | it('should be matched with the expected result', () => { 47 | const type = PAYLOAD_TYPES[DATA_UPLOAD].charAt(0).toUpperCase() + 48 | PAYLOAD_TYPES[DATA_UPLOAD].slice(1); 49 | const payload = createDataPayload('9eca7128409f609b2a72fc24985645665bbb99152b4b14261c3c3c93fb17cf54'); 50 | const target = '0a209eca7128409f609b2a72fc24985645665bbb99152b4b14261c3c3c93fb17cf54'; 51 | expect(genPayloadPb(payload, type)).to.be.eql(target); 52 | expect(recoverPayloadWithType(target, DATA_UPLOAD)).to.be.eql(payload); 53 | }); 54 | }); 55 | 56 | describe('# createDefaultPayload', () => { 57 | it('should be matched with the expected result', () => { 58 | const type = PAYLOAD_TYPES[VALUE_TRANSFER].charAt(0).toUpperCase() + 59 | PAYLOAD_TYPES[VALUE_TRANSFER].slice(1); 60 | const payload = createDefaultPayload('Hello MediBloc'); 61 | const target = '0a0e48656c6c6f204d656469426c6f63'; 62 | expect(genPayloadPb(payload, type)).to.be.eql(target); 63 | expect(recoverPayloadWithType(target, VALUE_TRANSFER)).to.be.eql(payload); 64 | }); 65 | }); 66 | 67 | describe('# createRevokeCertificationPayload', () => { 68 | it('should be matched with the expected result', () => { 69 | const type = PAYLOAD_TYPES[REVOKE_CERTIFICATION].charAt(0).toUpperCase() + 70 | PAYLOAD_TYPES[REVOKE_CERTIFICATION].slice(1); 71 | const payload = createRevokeCertificationPayload('9eca7128409f609b2a72fc24985645665bbb99152b4b14261c3c3c93fb17cf54'); 72 | const target = '0a209eca7128409f609b2a72fc24985645665bbb99152b4b14261c3c3c93fb17cf54'; 73 | expect(genPayloadPb(payload, type)).to.be.eql(target); 74 | expect(recoverPayloadWithType(target, REVOKE_CERTIFICATION)).to.be.eql(payload); 75 | }); 76 | }); 77 | 78 | describe('# createVotePayload', () => { 79 | it('should be matched with the expected result', () => { 80 | const type = PAYLOAD_TYPES[VOTE].charAt(0).toUpperCase() + 81 | PAYLOAD_TYPES[VOTE].slice(1); 82 | const payload = createVotePayload(['03528fa3684218f32c9fd7726a2839cff3ddef49d89bf4904af11bc12335f7c939', 83 | '03e7b794e1de1851b52ab0b0b995cc87558963265a7b26630f26ea8bb9131a7e21']); 84 | const target = '0a2103528fa3684218f32c9fd7726a2839cff3ddef49d89bf4904af11bc12335f7c9390a2103e7b794e1de1851b52ab0b0b995cc87558963265a7b26630f26ea8bb9131a7e21'; 85 | expect(genPayloadPb(payload, type)).to.be.eql(target); 86 | expect(recoverPayloadWithType(target, VOTE)).to.be.eql(payload); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /test/local/transaction/tx_valueTransfer.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { encryptKey } from 'cryptography/encrypt'; 3 | import { Account } from 'local/account'; 4 | import { 5 | createDefaultPayload, 6 | valueTransferTx, 7 | recoverPayload, 8 | } from 'local/transaction'; 9 | import { constants } from 'local/transaction/utils'; 10 | 11 | const { 12 | REQUIRED_TX_PARAMS, 13 | VALUE_TRANSFER, 14 | } = constants; 15 | 16 | // overall valueTransferTx 17 | describe('# valueTransferTx function', () => { 18 | const user = new Account(''); 19 | const valueTransferTxData = { 20 | from: user.pubKey, 21 | to: user.pubKey, 22 | value: '1000', 23 | nonce: 1, 24 | type: 'transfer', 25 | }; 26 | 27 | describe('# TX validation', () => { 28 | it('It should have \'from\' field', () => { 29 | const tempTxData = Object.assign( 30 | {}, 31 | valueTransferTxData, 32 | { 33 | from: undefined, 34 | }, 35 | ); 36 | expect(() => valueTransferTx(tempTxData)).to.throw(Error, 'Transaction should have from field.'); 37 | }); 38 | 39 | it('It should have adequate transfer value', () => { 40 | const tempTxData = Object.assign( 41 | {}, 42 | valueTransferTxData, 43 | { 44 | value: -5, 45 | }, 46 | ); 47 | expect(() => valueTransferTx(tempTxData)).to.throw(Error, 'Type of value need to be string'); 48 | }); 49 | }); 50 | 51 | describe('# TX hash', () => { 52 | it('Should be matched with go-medibloc', () => { 53 | // DATA from go-medibloc 54 | const dataFromGo = { 55 | from: '03528fa3684218f32c9fd7726a2839cff3ddef49d89bf4904af11bc12335f7c939', 56 | to: '03e7b794e1de1851b52ab0b0b995cc87558963265a7b26630f26ea8bb9131a7e21', 57 | value: '10', 58 | nonce: 1, 59 | }; 60 | const txHashFromGo = '8ffceecfa9df5680cb342e7bea9c6fcc8d5f549d4f2e527743bf61c76e9dc69c'; 61 | const txFromGo = valueTransferTx(dataFromGo); 62 | expect(txFromGo.hash).to.be.equal(txHashFromGo); 63 | }); 64 | }); 65 | 66 | describe('# TX signature', () => { 67 | const privKeyFromGo = 'ee8ea71e9501306fdd00c6e58b2ede51ca125a583858947ff8e309abf11d37ea'; 68 | const hashFromGo = '398b3bddcdcee2e5390ae3538429fd73f9443ce0cdec6dda21bc060ec568b135'; 69 | const signatureFromGo = '79f7335918d23ebf7a0506597b42f57a3c1703d4781d53c2427d6c4360c1c2b0566f684f14465882cbb0e98538fa9865f72829ccb14c548c320f08b5a37b5c4f01'; 70 | const encryptedPrivKey = encryptKey('passphrase', privKeyFromGo); 71 | const tx = valueTransferTx(valueTransferTxData); 72 | it('Should be matched with go-medibloc', () => { 73 | user.encryptedPrivKey = encryptedPrivKey; 74 | tx.hash = hashFromGo; 75 | user.signTx(tx, 'passphrase'); 76 | expect(tx.sign).to.be.equal(signatureFromGo); 77 | }); 78 | 79 | it('Throw error if user put unmatched passphrase', () => { 80 | expect(() => user.signTx(tx, 'wrongPassphrase')).to.throw(Error); 81 | }); 82 | }); 83 | }); 84 | 85 | describe('# valueTransferTx', () => { 86 | const payload = createDefaultPayload('Hello MediBloc!'); 87 | const fields = { 88 | from: '02bdc97dfc02502c5b8301ff46cbbb0dce56cd96b0af75edc50560630de5b0a472', 89 | nonce: 1, 90 | payload, 91 | to: '03e7b794e1de1851b52ab0b0b995cc87558963265a7b26630f26ea8bb9131a7e21', 92 | value: '10', 93 | }; 94 | const tx = valueTransferTx(fields); 95 | const txHashFromGo = 'ba79e5b2e128e4da138d1d42204643c249131be80fb13c46a78f53b05a464588'; 96 | 97 | it('should return transaction contains hash', () => { 98 | expect(tx).to.have.property('hash') 99 | .to.equal(txHashFromGo); 100 | }); 101 | it('should return transaction contains rawTx', () => { 102 | expect(tx).to.have.property('rawTx') 103 | .to.contain.all.keys(REQUIRED_TX_PARAMS[VALUE_TRANSFER].map(param => param.split('.')[0])); 104 | }); 105 | it('should return transaction not contains signature', () => { 106 | expect(tx).to.have.property('sign') 107 | .to.equal(null); 108 | }); 109 | 110 | describe('# recoverPayload', () => { 111 | it('should recover expected transaction payload', () => { 112 | expect(recoverPayload(tx)).to.eql(payload); 113 | }); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /docs/panaceajs-identification.rst: -------------------------------------------------------------------------------- 1 | .. _identification: 2 | 3 | .. include:: include_announcement.rst 4 | 5 | ==================== 6 | panaceajs.identification 7 | ==================== 8 | 9 | The ``panaceajs.identification`` contains identification functions. 10 | 11 | To use this package standalone use: 12 | 13 | .. code-block:: javascript 14 | 15 | var Id = require('@medibloc/panacea-js').identification; 16 | // 17 | // Instead, you can import from panaceajs like below. 18 | // 19 | // var Panaceajs = require('@medibloc/panacea-js'); 20 | // var panaceajs = Panaceajs.init(['http://localhost:9921']); 21 | // var Id = panaceajs.identification; 22 | 23 | --------------------------------------------------------------------------- 24 | 25 | createCertificate 26 | ============================== 27 | 28 | .. code-block:: javascript 29 | 30 | Id.createCertificate({ expireDate, issuer, issuerAccount, issueDate, passphrase, pubKey }); 31 | 32 | To create certificate, you can use ``Id.createCertificate({ expireDate, issuer, issuerAccount, issueDate, passphrase, pubKey })``. It generates certificate object which contains issuer's signature. 33 | 34 | ---------- 35 | Parameters 36 | ---------- 37 | 38 | ``Object`` 39 | 40 | - ``expireDate`` - ``Number`` : The unix timestamp when certificate is expired. 41 | - ``issuer`` - ``String`` : The issuer's url to check certificate authenticity. 42 | - ``issuerAccount`` - ``Object`` : The certificate issuer's account object from ``new Account()``. 43 | - ``issueDate`` - ``Number`` : The unix timestamp when issuing certificate. 44 | - ``passphrase`` - ``String`` : The passphrase for the issuerAccount. Passphrase is used to decrypt private key from issuerAccount's ``encryptedPrivKey``. 45 | - ``pubKey`` - ``String`` : The public key which to be certified by issuer. 46 | 47 | ------- 48 | Returns 49 | ------- 50 | 51 | ``Object`` : The certificate object. 52 | 53 | - ``expireDate`` - ``Number`` : The unix timestamp when certificate is expired. 54 | - ``issuer`` - ``String`` : The issuer's url to check certificate authenticity. 55 | - ``issueDate`` - ``Number`` : The unix timestamp when issuing certificate. 56 | - ``pubKey`` - ``String`` : The public key which certified by the certificate. 57 | - ``signature`` - ``String`` : The signature of issuer to certificate object. 58 | 59 | ------- 60 | Example 61 | ------- 62 | 63 | .. code-block:: javascript 64 | 65 | var issuer = new Account(); 66 | Id.createCertificate({ 67 | expireDate: Date.now() + (365 * 24 * 60 * 60 * 1000), 68 | issuer: 'https://medibloc.org', 69 | issuerAccount: issuer, 70 | issueDate: Date.now(), 71 | passphrase: '', 72 | pubKey: '031ae654051968bb57de12e36184fd9118c03d49e6c1d05ef99149074c31a8dcee', 73 | }); 74 | > { 75 | expireDate: 1558588202729, 76 | issuer: 'https://medibloc.org', 77 | issueDate: 1527052202729, 78 | pubKey: '031ae654051968bb57de12e36184fd9118c03d49e6c1d05ef99149074c31a8dcee', 79 | signature: '520282dce69b18f2dfefad8345a68e26a7b84ded32bc64e5a43cf0743e35a946629bc4245fe814f40acd196d19d5f9afcec4f185aae936491a8ad0fc9e73224501', 80 | } 81 | 82 | --------------------------------------------------------------------------- 83 | 84 | verifyCertificate 85 | ============================== 86 | 87 | .. code-block:: javascript 88 | 89 | Id.verifyCertificate(certificate, timeStamp, issuerPubKey); 90 | 91 | To verify certificate, you can use ``Id.verifyCertificate(certificate, timeStamp, issuerPubKey)``. 92 | 93 | ---------- 94 | Parameters 95 | ---------- 96 | 97 | 1. ``certificate`` - ``Object`` : The certificate object from ``createCertificate()`` 98 | 2. ``timeStamp`` - ``Number`` : The timeStamp to check whether the certificate is valid in the target time. 99 | 3. ``issuerPubKey`` - ``String`` : The issuerPubkey is the public key of the certificate issuer. 100 | 101 | ------- 102 | Returns 103 | ------- 104 | 105 | ``Boolean`` : True if the certificate is valid. 106 | 107 | ------- 108 | Example 109 | ------- 110 | 111 | .. code-block:: javascript 112 | 113 | var certificate = { 114 | expireDate: 1558588202729, 115 | issuer: 'https://medibloc.org', 116 | issueDate: 1527052202729, 117 | pubKey: '031ae654051968bb57de12e36184fd9118c03d49e6c1d05ef99149074c31a8dcee', 118 | signature: '520282dce69b18f2dfefad8345a68e26a7b84ded32bc64e5a43cf0743e35a946629bc4245fe814f40acd196d19d5f9afcec4f185aae936491a8ad0fc9e73224501' 119 | }; 120 | Id.verifyCertificate(certificate, Date.now(), '0253f338731d59180253be2a9eee8d8266948b23c71181a85df23b9883b19cb187') 121 | > true 122 | -------------------------------------------------------------------------------- /test/healthData/samples/careplan_sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "resourceType": "CarePlan", 3 | "id": "f202", 4 | "text": { 5 | "status": "generated", 6 | "div": "

Generated Narrative with Details

id: f202

contained: , , , , ,

status: active

intent: plan

subject: Roel

careTeam: id: careteam

addresses: Roel's head-neck tumor

goal: id: goal; status: in-progress; Elimination of the spenoid bone tumor (Details ); Roel

activity

outcomeReference: Roel's Chemotherapy

Details

-CategoryCodeStatusProhibitedProduct[x]
*Procedure (Details : {http://hl7.org/fhir/care-plan-activity-category code 'procedure' = 'Procedure)Chemotherapy (Details : {SNOMED CT code '367336001' = 'Chemotherapy', given as 'Chemotherapy'})in-progressfalseid: tpf; TPF (Details )
" 7 | }, 8 | "contained": [ 9 | { 10 | "resourceType": "Medication", 11 | "id": "doce", 12 | "code": { 13 | "coding": [ 14 | { 15 | "system": "http://snomed.info/sct", 16 | "code": "108806006", 17 | "display": "Docetaxel" 18 | } 19 | ] 20 | } 21 | }, 22 | { 23 | "resourceType": "Medication", 24 | "id": "cisp", 25 | "code": { 26 | "coding": [ 27 | { 28 | "system": "http://snomed.info/sct", 29 | "code": "57066004", 30 | "display": "Cisplatin" 31 | } 32 | ] 33 | } 34 | }, 35 | { 36 | "resourceType": "Medication", 37 | "id": "fluo", 38 | "code": { 39 | "coding": [ 40 | { 41 | "system": "http://snomed.info/sct", 42 | "code": "3127006", 43 | "display": "Fluorouracil" 44 | } 45 | ] 46 | } 47 | }, 48 | { 49 | "resourceType": "Medication", 50 | "id": "tpf", 51 | "code": { 52 | "text": "TPF" 53 | }, 54 | "ingredient": [ 55 | { 56 | "itemReference": { 57 | "reference": "#doce" 58 | } 59 | }, 60 | { 61 | "itemReference": { 62 | "reference": "#cisp" 63 | } 64 | }, 65 | { 66 | "itemReference": { 67 | "reference": "#fluo" 68 | } 69 | } 70 | ] 71 | }, 72 | { 73 | "resourceType": "CareTeam", 74 | "id": "careteam", 75 | "participant": [ 76 | { 77 | "role": { 78 | "coding": [ 79 | { 80 | "system": "http://snomed.info/sct", 81 | "code": "28995006", 82 | "display": "Treated with" 83 | } 84 | ] 85 | }, 86 | "member": { 87 | "reference": "Practitioner/f201", 88 | "display": "Dokter Bronsig" 89 | } 90 | } 91 | ] 92 | }, 93 | { 94 | "resourceType": "Goal", 95 | "id": "goal", 96 | "status": "in-progress", 97 | "description": { 98 | "text": "Elimination of the spenoid bone tumor" 99 | }, 100 | "subject": { 101 | "reference": "Patient/f201", 102 | "display": "Roel" 103 | } 104 | } 105 | ], 106 | "status": "active", 107 | "intent": "plan", 108 | "subject": { 109 | "reference": "Patient/f201", 110 | "display": "Roel" 111 | }, 112 | "careTeam": [ 113 | { 114 | "reference": "#careteam" 115 | } 116 | ], 117 | "addresses": [ 118 | { 119 | "reference": "Condition/f202", 120 | "display": "Roel's head-neck tumor" 121 | } 122 | ], 123 | "goal": [ 124 | { 125 | "reference": "#goal" 126 | } 127 | ], 128 | "activity": [ 129 | { 130 | "outcomeReference": [ 131 | { 132 | "reference": "Procedure/f201", 133 | "display": "Roel's Chemotherapy" 134 | } 135 | ], 136 | "detail": { 137 | "category": { 138 | "coding": [ 139 | { 140 | "system": "http://hl7.org/fhir/care-plan-activity-category", 141 | "code": "procedure" 142 | } 143 | ] 144 | }, 145 | "code": { 146 | "coding": [ 147 | { 148 | "system": "http://snomed.info/sct", 149 | "code": "367336001", 150 | "display": "Chemotherapy" 151 | } 152 | ] 153 | }, 154 | "status": "in-progress", 155 | "prohibited": false, 156 | "productReference": { 157 | "reference": "#tpf" 158 | } 159 | } 160 | } 161 | ] 162 | } -------------------------------------------------------------------------------- /test/client/gateway.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import chaiAsPromised from 'chai-as-promised'; 3 | import gateway from 'client/gateway'; 4 | import sinon from 'sinon'; 5 | import sinonChai from 'sinon-chai'; 6 | import proxyquire from 'proxyquire'; 7 | import httpRequest from 'client/httpRequest'; 8 | import nodeBucket from 'client/nodeBucket'; 9 | import { GET, MAX_REQUEST_RETRY_COUNT } from 'client/constants'; 10 | 11 | [sinonChai, chaiAsPromised].forEach(plugin => chai.use(plugin)); 12 | 13 | describe('gateway', () => { 14 | const invalidNodes = ['first_invalid_url', 'second_invalid_node']; 15 | const validNodes = ['first_valid_node', 'second_valid_node']; 16 | const combinedNodes = invalidNodes.concat(validNodes); 17 | 18 | const defaultReqConfig = { 19 | method: GET, 20 | path: 'v1/user/account', 21 | payload: { 22 | address: 'address', 23 | height: 0, 24 | }, 25 | }; 26 | 27 | const defaultReqResult = { 28 | balance: 1000, 29 | nonce: '0', 30 | type: 0, 31 | }; 32 | 33 | describe('#returned object', () => { 34 | let validGateway; 35 | beforeEach(() => { 36 | const bucket = nodeBucket(validNodes.slice()); 37 | validGateway = gateway(bucket); 38 | return Promise.resolve(); 39 | }); 40 | 41 | it('should be a object', () => { 42 | return expect(validGateway) 43 | .to.be.an('object'); 44 | }); 45 | 46 | it('should have a sendRequest function', () => { 47 | return expect(validGateway) 48 | .to.be.property('sendRequest') 49 | .to.be.an('function'); 50 | }); 51 | 52 | it('should throw an error if bucket argument is empty', () => { 53 | return expect(() => gateway()) 54 | .to.throw(Error, 'gateway requires bucket for initialization.'); 55 | }); 56 | }); 57 | 58 | describe('#sendRequest', () => { 59 | let combinedGateway; 60 | let invalidGateway; 61 | let proxyGateway; 62 | let requestStub; 63 | let sendRequestSpyCG; 64 | let sendRequestSpyIG; 65 | let sendRequestSpyVG; 66 | let validGateway; 67 | beforeEach(() => { 68 | requestStub = sinon.stub(httpRequest, 'request'); 69 | 70 | requestStub.withArgs(sinon.match((object) => { 71 | return object.baseURL === validNodes[0]; 72 | })).resolves({ ...defaultReqResult }); 73 | requestStub.withArgs(sinon.match((object) => { 74 | return object.baseURL === validNodes[1]; 75 | })).resolves({ ...defaultReqResult }); 76 | requestStub.withArgs(sinon.match((object) => { 77 | return object.baseURL === invalidNodes[0]; 78 | })).throws(); 79 | requestStub.withArgs(sinon.match((object) => { 80 | return object.baseURL === invalidNodes[1]; 81 | })).throws(); 82 | proxyGateway = proxyquire('../../src/client/gateway', { 83 | '/.httpRequest': { 84 | request: requestStub, 85 | }, 86 | }); 87 | 88 | const combinedBucket = nodeBucket(combinedNodes.slice()); 89 | combinedGateway = proxyGateway(combinedBucket); 90 | sendRequestSpyCG = sinon.spy(combinedGateway, 'sendRequest'); 91 | 92 | const invalidBucket = nodeBucket(invalidNodes.slice()); 93 | invalidGateway = proxyGateway(invalidBucket); 94 | sendRequestSpyIG = sinon.spy(invalidGateway, 'sendRequest'); 95 | 96 | const validBucket = nodeBucket(validNodes.slice()); 97 | validGateway = proxyGateway(validBucket); 98 | sendRequestSpyVG = sinon.spy(validGateway, 'sendRequest'); 99 | 100 | return Promise.resolve(); 101 | }); 102 | 103 | afterEach(() => { 104 | requestStub.restore(); 105 | sendRequestSpyCG.restore(); 106 | sendRequestSpyIG.restore(); 107 | sendRequestSpyVG.restore(); 108 | }); 109 | 110 | it('should be correct', () => { 111 | return validGateway.sendRequest(defaultReqConfig).then((res) => { 112 | expect(requestStub).to.be.calledWith; 113 | expect(sendRequestSpyVG).to.be.calledOnce; 114 | return expect(res).to.be.eql(defaultReqResult); 115 | }); 116 | }); 117 | 118 | it('should throw an error if retry count exceed MAX_REQUEST_RETRY_COUNT', () => { 119 | return validGateway.sendRequest(defaultReqConfig, {}, MAX_REQUEST_RETRY_COUNT + 1) 120 | .catch((err) => { 121 | expect(requestStub).not.to.be.called; 122 | expect(sendRequestSpyVG).to.be.calledOnce; 123 | return expect(err.message).to.be.eql('send request failed.'); 124 | }); 125 | }); 126 | 127 | // it('should throw an error if retry count exceed bucket size', () => { 128 | // return invalidGateway.sendRequest(defaultReqConfig).catch((err) => { 129 | // expect(requestStub).not.to.be.calledThrice; 130 | // // expect(sendRequestSpyIG).to.be.calledThrice; 131 | // console.log(err); 132 | // return expect(err.data.error).to.be.eql('max request retry count exceeded.'); 133 | // }); 134 | // }); 135 | // 136 | // it('should be correct although if some nodes are invalid', () => { 137 | // return combinedGateway.sendRequest(defaultReqConfig).then((res) => { 138 | // expect(requestStub).to.be.calledThrice; 139 | // // 140 | // console.log(res); 141 | // return expect(res).to.be.eql(defaultReqResult); 142 | // }); 143 | // }); 144 | }); 145 | }); 146 | -------------------------------------------------------------------------------- /src/healthData/proto/stu3min/common.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package buffer; 4 | 5 | message Meta { 6 | string versionId = 1; 7 | string lastUpdated = 2; 8 | } 9 | 10 | message Qualification { 11 | Code code = 1; 12 | Period period = 2; 13 | repeated Identifier identifier = 3; 14 | Issuer issuer = 4; 15 | } 16 | 17 | message Subject { 18 | string display = 1; 19 | string reference = 2; 20 | } 21 | 22 | message Stage { 23 | repeated Coding coding = 1; 24 | } 25 | 26 | message Type { 27 | string text = 1; 28 | repeated Coding coding = 2; 29 | } 30 | 31 | message Patient { 32 | string display = 1; 33 | string reference = 2; 34 | } 35 | 36 | message EncounterRef { 37 | string reference = 1; 38 | } 39 | 40 | message Requester { 41 | string display = 1; 42 | string reference = 2; 43 | } 44 | 45 | message Performer { 46 | string display = 1; 47 | string reference = 2; 48 | } 49 | 50 | message Context { 51 | string display = 1; 52 | string reference = 2; 53 | } 54 | 55 | message Category { 56 | string text = 1; 57 | repeated Coding coding = 2; 58 | } 59 | 60 | message Issuer { 61 | string display = 1; 62 | } 63 | 64 | message Code { 65 | string text = 1; 66 | repeated Coding coding = 2; 67 | } 68 | 69 | message PractitionerRole { 70 | Organization organization = 1; 71 | repeated Location location = 2; 72 | Role role = 3; 73 | Period period = 4; 74 | repeated HealthcareService healthcareService = 5; 75 | } 76 | 77 | message HealthcareService { 78 | string reference = 1; 79 | } 80 | 81 | message Period { 82 | string start = 1; 83 | string end = 2; 84 | } 85 | 86 | message Role { 87 | string text = 1; 88 | repeated Coding coding = 2; 89 | repeated string fhir_comments = 3; 90 | } 91 | 92 | message Location { 93 | string display = 1; 94 | string reference = 2; 95 | } 96 | 97 | message Organization { 98 | string reference = 1; 99 | } 100 | 101 | message Name { 102 | string use = 1; 103 | string text = 2; 104 | string family = 3; 105 | repeated string given = 4; 106 | repeated string prefix = 5; 107 | repeated string suffix = 6; 108 | Period period = 7; 109 | } 110 | 111 | message Identifier { 112 | string use = 1; 113 | Assigner assigner = 2; 114 | string system = 3; 115 | string value = 4; 116 | Period period = 5; 117 | Type type = 6; 118 | } 119 | 120 | message Assigner { 121 | string display = 1; 122 | } 123 | 124 | message Contact { 125 | Name name = 1; 126 | repeated Relationship relationship = 2; 127 | repeated Telecom telecom = 3; 128 | string gender = 4; 129 | Period period = 5; 130 | Address address = 6; 131 | } 132 | 133 | message Address { 134 | string city = 1; 135 | string use = 2; 136 | string district = 3; 137 | Period period = 4; 138 | string state = 5; 139 | string postalCode = 6; 140 | repeated string line = 7; 141 | string type = 8; 142 | string country = 9; 143 | string text = 10; 144 | } 145 | 146 | message Relationship { 147 | repeated Coding coding = 1; 148 | } 149 | 150 | message Extension { 151 | string url = 1; 152 | string valueCode = 2; 153 | } 154 | 155 | message Telecom { 156 | string use = 1; 157 | string system = 2; 158 | string value = 3; 159 | uint32 rank = 4; 160 | Period period = 5; 161 | } 162 | 163 | message Communication { 164 | Language language = 1; 165 | bool preferred = 2; 166 | } 167 | 168 | message Language { 169 | string text = 1; 170 | repeated Coding coding = 2; 171 | } 172 | 173 | message Coding { 174 | string code = 1; 175 | string system = 2; 176 | string display = 3; 177 | } 178 | 179 | message Text { 180 | string status = 1; 181 | string div = 2; 182 | } 183 | 184 | message ManagingOrganization { 185 | string display = 1; 186 | string reference = 2; 187 | } 188 | 189 | // Common between diagnostic report and Condition 190 | 191 | message Summary { 192 | repeated Coding coding = 1; 193 | } 194 | 195 | message Asserter { 196 | string display = 1; 197 | string reference = 2; 198 | } 199 | 200 | message BodySite { 201 | repeated Coding coding = 1; 202 | } 203 | 204 | 205 | message Severity { 206 | repeated Coding coding = 1; 207 | } 208 | 209 | message Evidence { 210 | repeated Code code = 1; 211 | } 212 | 213 | // bundle and search 214 | 215 | message Link { 216 | string url = 1; 217 | string relation = 2; 218 | } 219 | 220 | // diagnostic report and medicaiton request 221 | 222 | 223 | message Reason { 224 | repeated Coding coding = 1; 225 | } 226 | 227 | message BasedOn { 228 | string reference = 1; 229 | } 230 | 231 | // medication and allergy 232 | 233 | message Note { 234 | string text = 1; 235 | } 236 | 237 | // encounter containd 238 | message Contained { 239 | string resourceType = 1; 240 | Code code = 2; 241 | string id = 3; 242 | } 243 | 244 | // diagnostic report and procedure 245 | 246 | message Actor { 247 | string display = 1; 248 | string reference = 2; 249 | } 250 | 251 | // encounter and goal 252 | 253 | 254 | message Priority { 255 | string text = 1; 256 | repeated Coding coding = 2; 257 | } 258 | 259 | // careplan and gola 260 | 261 | message Addresses { 262 | string display = 1; 263 | string reference = 2; 264 | } -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | # import os 16 | # import sys 17 | # sys.path.insert(0, os.path.abspath('.')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = u'panacea-js' 23 | copyright = u'2018, MediBloc' 24 | author = u'MediBloc' 25 | 26 | # The short X.Y version 27 | version = u'1.0' 28 | # The full version, including alpha/beta/rc tags 29 | release = u'1.0.1' 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ 42 | 43 | ] 44 | 45 | # Add any paths that contain templates here, relative to this directory. 46 | templates_path = ['_templates'] 47 | 48 | # The suffix(es) of source filenames. 49 | # You can specify multiple suffix as a list of string: 50 | # 51 | # source_suffix = ['.rst', '.md'] 52 | source_suffix = '.rst' 53 | 54 | # The master toctree document. 55 | master_doc = 'index' 56 | 57 | # The language for content autogenerated by Sphinx. Refer to documentation 58 | # for a list of supported languages. 59 | # 60 | # This is also used if you do content translation via gettext catalogs. 61 | # Usually you set "language" from the command line for these cases. 62 | language = None 63 | 64 | # List of patterns, relative to source directory, that match files and 65 | # directories to ignore when looking for source files. 66 | # This pattern also affects html_static_path and html_extra_path . 67 | exclude_patterns = [u'_build', 'Thumbs.db', '.DS_Store'] 68 | 69 | # The name of the Pygments (syntax highlighting) style to use. 70 | pygments_style = 'sphinx' 71 | 72 | 73 | # -- Options for HTML output ------------------------------------------------- 74 | 75 | # The theme to use for HTML and HTML Help pages. See the documentation for 76 | # a list of builtin themes. 77 | # 78 | html_theme = 'sphinx_rtd_theme' 79 | 80 | # Theme options are theme-specific and customize the look and feel of a theme 81 | # further. For a list of options available for each theme, see the 82 | # documentation. 83 | # 84 | # html_theme_options = {} 85 | 86 | # Add any paths that contain custom static files (such as style sheets) here, 87 | # relative to this directory. They are copied after the builtin static files, 88 | # so a file named "default.css" will overwrite the builtin "default.css". 89 | html_static_path = [] 90 | 91 | # Custom sidebar templates, must be a dictionary that maps document names 92 | # to template names. 93 | # 94 | # The default sidebars (for documents that don't match any pattern) are 95 | # defined by theme itself. Builtin themes are using these templates by 96 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 97 | # 'searchbox.html']``. 98 | # 99 | # html_sidebars = {} 100 | 101 | 102 | # -- Options for HTMLHelp output --------------------------------------------- 103 | 104 | # Output file base name for HTML help builder. 105 | htmlhelp_basename = 'panaceajsdoc' 106 | 107 | 108 | # -- Options for LaTeX output ------------------------------------------------ 109 | 110 | latex_elements = { 111 | # The paper size ('letterpaper' or 'a4paper'). 112 | # 113 | # 'papersize': 'letterpaper', 114 | 115 | # The font size ('10pt', '11pt' or '12pt'). 116 | # 117 | # 'pointsize': '10pt', 118 | 119 | # Additional stuff for the LaTeX preamble. 120 | # 121 | # 'preamble': '', 122 | 123 | # Latex figure (float) alignment 124 | # 125 | # 'figure_align': 'htbp', 126 | } 127 | 128 | # Grouping the document tree into LaTeX files. List of tuples 129 | # (source start file, target name, title, 130 | # author, documentclass [howto, manual, or own class]). 131 | latex_documents = [ 132 | (master_doc, 'panaceajs.tex', u'panaceajs Documentation', 133 | u'ggomma', 'manual'), 134 | ] 135 | 136 | 137 | # -- Options for manual page output ------------------------------------------ 138 | 139 | # One entry per manual page. List of tuples 140 | # (source start file, name, description, authors, manual section). 141 | man_pages = [ 142 | (master_doc, 'panaceajs', u'panaceajs Documentation', 143 | [author], 1) 144 | ] 145 | 146 | 147 | # -- Options for Texinfo output ---------------------------------------------- 148 | 149 | # Grouping the document tree into Texinfo files. List of tuples 150 | # (source start file, target name, title, author, 151 | # dir menu entry, description, category) 152 | texinfo_documents = [ 153 | (master_doc, 'panaceajs', u'panaceajs Documentation', 154 | author, 'panaceajs', 'One line description of project.', 155 | 'Miscellaneous'), 156 | ] 157 | -------------------------------------------------------------------------------- /test/local/account/account.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { randomBytes } from 'crypto'; 3 | import { getPubKey } from 'cryptography'; 4 | import { Account } from 'local/account'; 5 | import Transaction from '../../../src/local/transaction'; 6 | 7 | // Account 8 | describe('# Account class', () => { 9 | let account; 10 | let passphrase; 11 | beforeEach(() => { 12 | passphrase = 'medibloc'; 13 | account = new Account(passphrase); 14 | return Promise.resolve(); 15 | }); 16 | describe('can generate account object', () => { 17 | it('Account object can be made without any arguments', () => { 18 | const newAccount = new Account(); 19 | expect(newAccount).to.have.own.property('encryptedPrivKey'); 20 | expect(newAccount).to.have.own.property('pubKey'); 21 | }); 22 | it('Account object can be made with passphrase', () => { 23 | expect(account).to.have.own.property('encryptedPrivKey'); 24 | expect(account).to.have.own.property('pubKey'); 25 | }); 26 | it('Account object can not be made with decrypted privKey', () => { 27 | const privKey = randomBytes(32).toString('hex'); 28 | expect(() => new Account(passphrase, privKey)).to.throw(Error); 29 | }); 30 | it('Account object can be made with encrypted privKey', () => { 31 | const newAccount = new Account(passphrase, account.encryptedPrivKey); 32 | expect(newAccount).to.have.own.property('encryptedPrivKey') 33 | .to.eq(account.encryptedPrivKey); 34 | expect(newAccount).to.have.own.property('pubKey') 35 | .to.equal(account.pubKey); 36 | }); 37 | it('Account object can be made with encrypted privKey and pubKey', () => { 38 | const newAccount = new Account(null, account.encryptedPrivKey, account.pubKey); 39 | expect(newAccount).to.have.own.property('encryptedPrivKey') 40 | .to.eq(account.encryptedPrivKey); 41 | expect(newAccount).to.have.own.property('pubKey') 42 | .to.equal(account.pubKey); 43 | }); 44 | it('Get decrypted private key with appropriate passphrase', () => { 45 | const decryptedPrivKey = account.getDecryptedPrivateKey(passphrase); 46 | expect(getPubKey(decryptedPrivKey)).to.be.equal(account.pubKey); 47 | }); 48 | it('Get decrypted private key with appropriate passphrase', () => { 49 | const wrongPassphrase = 'medibloc!'; 50 | expect(() => account.getDecryptedPrivateKey(wrongPassphrase)).to.throw(Error, 'Key derivation failed - possibly wrong passphrase'); 51 | }); 52 | }); 53 | }); 54 | 55 | describe('# Account object', () => { 56 | let account1; 57 | let account2; 58 | const passphrase1 = 'MediBloc1!'; 59 | const passphrase2 = 'MediBloc2@'; 60 | const pubKey1 = '028b51f14da514bd29683da96c39b07ca9a5c008c3c5d392fe5f16db36388e73d1'; 61 | const pubKey2 = '03c236cdff9cbd4a1e896dc2ea8b30f6ce2afe14a6da4a5aaec176970b519ed9bf'; 62 | const encryptedPrivKey1 = { 63 | version: 3, 64 | id: '33ed4df1-e57b-436f-8cdc-29a8cc359503', 65 | address: '028b51f14da514bd29683da96c39b07ca9a5c008c3c5d392fe5f16db36388e73d1', 66 | crypto: { 67 | ciphertext: '2e8a30aad53bb99c7c5913425feeef84033f00a33157e674672cc5aceb91db03c3032a36bdb19cb1673a0121d3a7c26e436cb550132571555455542ef95693de', 68 | cipherparams: { 69 | iv: '972e35a23ca7f41b1becfb1f6db6cbff', 70 | }, 71 | cipher: 'aes-128-ctr', 72 | kdf: 'scrypt', 73 | kdfparams: { 74 | dklen: 32, 75 | salt: '4f0080d7939f3902443703b13613a3ff25ccd2c1c3b6d666026048f49b83aff1', 76 | n: 8192, 77 | r: 8, 78 | p: 1, 79 | }, 80 | mac: '052e319223f86812c34e61921908dc133a81cb427a494a778aa19803e3907dbc', 81 | }, 82 | }; 83 | const encryptedPrivKey2 = { 84 | version: 3, 85 | id: '0952fdd3-e2f7-44df-b22c-d092eeb6be5b', 86 | address: '03c236cdff9cbd4a1e896dc2ea8b30f6ce2afe14a6da4a5aaec176970b519ed9bf', 87 | crypto: { 88 | ciphertext: '600e485cdf5defa5bc1b234c781ffab3bf562e25e4550510cdd027d6e5dbafce90017deda9cd40f5cfdd5817dd7d8dd4d73f6e5a579bba9cae0ad692ac2d8fd5', 89 | cipherparams: { 90 | iv: '48d6e2e5aaf9efb00a08acb430a3b8fa', 91 | }, 92 | cipher: 'aes-128-ctr', 93 | kdf: 'scrypt', 94 | kdfparams: { 95 | dklen: 32, 96 | salt: '3d36647a323c3400eb89e6639dd5b1b4d013a377412cdc1aee442fc84692ba77', 97 | n: 8192, 98 | r: 8, 99 | p: 1, 100 | }, 101 | mac: '599b11bf8390174a960fa990792dfc5dd0f36d9b9e48bc309cb2be410f9ee617', 102 | }, 103 | }; 104 | const payerSignFromGo = 'e8592d40e28ec1f38ce80ff46d07fc1896e67f1bd230806d4d4728092043129223674bbe2e7e5e986764b5bb9f15079e315680ce9c8aefe20adefbd8c89f2d8500'; 105 | beforeEach(() => { 106 | account1 = new Account(passphrase1, encryptedPrivKey1, pubKey1); 107 | account2 = new Account(passphrase2, encryptedPrivKey2, pubKey2); 108 | return Promise.resolve(); 109 | }); 110 | 111 | it('can sign transaction as payer', () => { 112 | const txHashFromGo = 'cb315e2f485d45e3c136b8795006c2bf1ff1a0ef6539d78cebffcb79424edb69'; 113 | const txData = { 114 | chain_id: 1, 115 | from: '028b51f14da514bd29683da96c39b07ca9a5c008c3c5d392fe5f16db36388e73d1', 116 | to: '03c236cdff9cbd4a1e896dc2ea8b30f6ce2afe14a6da4a5aaec176970b519ed9bf', 117 | nonce: 1, 118 | }; 119 | const tx = Transaction.valueTransferTx(txData); 120 | console.log(tx); 121 | account1.signTx(tx, 'MediBloc1!'); 122 | account2.signTxAsPayer(tx, 'MediBloc2@'); 123 | 124 | expect(tx.hash).to.equal(txHashFromGo); 125 | expect(tx.payerSign).to.equal(payerSignFromGo); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /src/cryptography/encrypt.js: -------------------------------------------------------------------------------- 1 | import { 2 | createCipheriv, 3 | createDecipheriv, 4 | pbkdf2Sync, 5 | randomBytes, 6 | } from 'crypto'; 7 | import scryptsy from 'scrypt.js'; 8 | import _ from 'underscore'; 9 | import uuidv4 from 'uuid/v4'; 10 | import { sha3 } from 'utils'; 11 | import { getPubKey } from './keyGen'; 12 | 13 | const decryptData = (accessKey = '', encryptedData) => { 14 | if (!encryptedData) { 15 | return null; 16 | } 17 | const algorithm = 'AES-256-CTR'; 18 | const textParts = encryptedData.split(':'); 19 | if (textParts.length < 2) { 20 | throw new Error('Invalid encrypted data format'); 21 | } 22 | const iv = Buffer.from(textParts.shift(), 'hex'); 23 | const encryptedText = Buffer.from(textParts.join(':'), 'hex'); 24 | const hashedAccessKey = sha3(accessKey); 25 | 26 | const decipher = createDecipheriv(algorithm, Buffer.from(hashedAccessKey, 'hex'), iv); 27 | const decryptedData = decipher.update(encryptedText, 'hex', 'utf8'); 28 | 29 | try { 30 | return decryptedData + decipher.final('utf8'); 31 | } catch (err) { 32 | throw new Error('Wrong Access Key'); 33 | } 34 | }; 35 | 36 | const decryptKey = (password, encryptedKey) => { 37 | if (!_.isString(password)) { 38 | throw new Error('Not a valid password'); 39 | } 40 | const json = (_.isObject(encryptedKey)) ? encryptedKey : JSON.parse(encryptedKey); 41 | 42 | if (json.version !== 3) { 43 | throw new Error('Not a V3 wallet'); 44 | } 45 | 46 | let derivedKey; 47 | let kdfParams; 48 | if (json.crypto.kdf === 'scrypt') { 49 | kdfParams = json.crypto.kdfparams; 50 | 51 | // FIXME: support progress reporting callback 52 | derivedKey = scryptsy(Buffer.from(password), Buffer.from(kdfParams.salt, 'hex'), kdfParams.n, kdfParams.r, kdfParams.p, kdfParams.dklen); 53 | } else if (json.crypto.kdf === 'pbkdf2') { 54 | kdfParams = json.crypto.kdfparams; 55 | 56 | if (kdfParams.prf !== 'hmac-sha256') { 57 | throw new Error('Unsupported parameters to PBKDF2'); 58 | } 59 | 60 | derivedKey = pbkdf2Sync(Buffer.from(password), Buffer.from(kdfParams.salt, 'hex'), kdfParams.c, kdfParams.dklen, 'sha256'); 61 | } else { 62 | throw new Error('Unsupported key derivation scheme'); 63 | } 64 | const ciphertext = Buffer.from(json.crypto.ciphertext, 'hex'); 65 | 66 | const mac = sha3(Buffer.concat([derivedKey.slice(16, 32), ciphertext])); 67 | if (mac.toString('hex') !== json.crypto.mac) { 68 | throw new Error('Key derivation failed - possibly wrong passphrase'); 69 | } 70 | 71 | const decipher = createDecipheriv(json.crypto.cipher, derivedKey.slice(0, 16), Buffer.from(json.crypto.cipherparams.iv, 'hex')); 72 | return ciphertext.length > 32 ? 73 | Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString() : 74 | Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('hex'); 75 | }; 76 | 77 | const encryptData = (accessKey = '', data) => { 78 | // TODO Need to get stream files also. 79 | if (!_.isString(data)) { 80 | throw new Error('Invalid msg type'); 81 | } 82 | 83 | const algorithm = 'AES-256-CTR'; 84 | const iv = randomBytes(16); 85 | const hashedAccessKey = sha3(accessKey); 86 | 87 | const cipher = createCipheriv(algorithm, Buffer.from(hashedAccessKey, 'hex'), iv); 88 | const encryptedText = `${cipher.update(data, 'utf8', 'hex')}${cipher.final('hex')}`; 89 | return `${iv.toString('hex')}:${encryptedText}`; 90 | }; 91 | 92 | // Taken from https://github.com/ethereumjs/ethereumjs-wallet 93 | const encryptKey = (password, privKey, options) => { 94 | const opts = options || {}; 95 | const salt = opts.salt || randomBytes(32); 96 | const iv = opts.iv || randomBytes(16); 97 | 98 | let derivedKey; 99 | const kdf = opts.kdf || 'scrypt'; 100 | const kdfparams = { 101 | dklen: opts.dklen || 32, 102 | salt: salt.toString('hex'), 103 | }; 104 | 105 | if (kdf === 'pbkdf2') { 106 | kdfparams.c = opts.c || 262144; 107 | kdfparams.prf = 'hmac-sha256'; 108 | derivedKey = pbkdf2Sync(Buffer.from(password), salt, kdfparams.c, kdfparams.dklen, 'sha256'); 109 | } else if (kdf === 'scrypt') { 110 | // FIXME: support progress reporting callback 111 | kdfparams.n = opts.n || 8192; 112 | kdfparams.r = opts.r || 8; 113 | kdfparams.p = opts.p || 1; 114 | derivedKey = scryptsy( 115 | Buffer.from(password), 116 | salt, 117 | kdfparams.n, 118 | kdfparams.r, 119 | kdfparams.p, 120 | kdfparams.dklen, 121 | ); 122 | } else { 123 | throw new Error('Unsupported kdf'); 124 | } 125 | 126 | const cipher = createCipheriv(opts.cipher || 'aes-128-ctr', derivedKey.slice(0, 16), iv); 127 | if (!cipher) { 128 | throw new Error('Unsupported cipher'); 129 | } 130 | 131 | const ciphertext = Buffer.concat([cipher.update(Buffer.from(privKey, 'hex')), cipher.final()]); 132 | 133 | const mac = sha3(Buffer.concat([derivedKey.slice(16, 32), Buffer.from(ciphertext, 'hex')])); 134 | 135 | return { 136 | version: 3, 137 | id: uuidv4({ random: opts.uuid || randomBytes(16) }), 138 | address: getPubKey(privKey), 139 | crypto: { 140 | ciphertext: ciphertext.toString('hex'), 141 | cipherparams: { 142 | iv: iv.toString('hex'), 143 | }, 144 | cipher: opts.cipher || 'aes-128-ctr', 145 | kdf, 146 | kdfparams, 147 | mac: mac.toString('hex'), 148 | }, 149 | }; 150 | }; 151 | 152 | // TODO 153 | // export const encryptDataStream = () => {}; 154 | 155 | // TODO 156 | // export const decryptDataStream = () => {}; 157 | 158 | export default { 159 | decryptData, 160 | decryptKey, 161 | encryptData, 162 | encryptKey, 163 | }; 164 | -------------------------------------------------------------------------------- /test/healthData/index.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import fs from 'fs'; 3 | import jsonfile from 'jsonfile'; 4 | import path from 'path'; 5 | import healthData from 'healthData'; 6 | 7 | describe('healthData', () => { 8 | describe('#returned object', () => { 9 | it('should be a object', () => { 10 | return expect(healthData) 11 | .to.be.an('object'); 12 | }); 13 | 14 | it('should have a decodeData', () => { 15 | return expect(healthData) 16 | .to.be.property('decodeData') 17 | .to.be.an('function'); 18 | }); 19 | 20 | it('should have a decodeDataFromFile', () => { 21 | return expect(healthData) 22 | .to.be.property('decodeDataFromFile') 23 | .to.be.an('function'); 24 | }); 25 | 26 | it('should have a encodeData', () => { 27 | return expect(healthData) 28 | .to.be.property('encodeData') 29 | .to.be.an('function'); 30 | }); 31 | 32 | it('should have a encodeDataFromFile', () => { 33 | return expect(healthData) 34 | .to.be.property('encodeDataFromFile') 35 | .to.be.an('function'); 36 | }); 37 | }); 38 | 39 | describe('#encode and decode', () => { 40 | it('should be work well for medical-fhir patient', () => { 41 | const filePath = path.resolve(__dirname, './samples/patient_sample.json'); 42 | return healthData.encodeDataFromFile( 43 | filePath, 44 | 'medical-fhir', 45 | 'patient', 46 | ).then((encodedData) => { 47 | return healthData.decodeData(encodedData).then((decodedData) => { 48 | const data = jsonfile.readFileSync(filePath); 49 | return expect(decodedData) 50 | .to.be.deep.equal(data); 51 | }); 52 | }); 53 | }); 54 | 55 | it('should be work well for medical-fhir observation', () => { 56 | const filePath = path.resolve(__dirname, './samples/observation_sample.json'); 57 | return healthData.encodeDataFromFile( 58 | filePath, 59 | 'medical-fhir', 60 | 'observation', 61 | ).then((encodedData) => { 62 | return healthData.decodeData(encodedData).then((decodedData) => { 63 | const data = jsonfile.readFileSync(filePath); 64 | return expect(decodedData) 65 | .to.be.deep.equal(data); 66 | }); 67 | }); 68 | }); 69 | 70 | it('should be work well for pghd txt format', () => { 71 | const filePath = path.resolve(__dirname, './samples/pghd.txt'); 72 | return healthData.encodeDataFromFile( 73 | filePath, 74 | 'pghd', 75 | null, 76 | ).then((encodedData) => { 77 | return healthData.decodeData(encodedData).then((decodedData) => { 78 | const data = fs.readFileSync(filePath, 'utf-8'); 79 | return expect(decodedData) 80 | .to.be.deep.equal(data); 81 | }); 82 | }); 83 | }); 84 | 85 | it('should be work well for pghd json format', () => { 86 | const filePath = path.resolve(__dirname, './samples/pghd.json'); 87 | return healthData.encodeDataFromFile( 88 | filePath, 89 | 'pghd', 90 | null, 91 | ).then((encodedData) => { 92 | return healthData.decodeData(encodedData).then((decodedData) => { 93 | const data = jsonfile.readFileSync(filePath); 94 | return expect(JSON.parse(decodedData)) 95 | .to.be.deep.equal(data); 96 | }); 97 | }); 98 | }); 99 | }); 100 | 101 | describe('#hashDataFromFile', () => { 102 | it('should be work well for medical-fhir patient', () => { 103 | const filePath = path.resolve(__dirname, './samples/patient_sample.json'); 104 | return healthData.hashDataFromFile( 105 | filePath, 106 | 'medical-fhir', 107 | 'patient', 108 | ).then((hash) => { 109 | return expect(hash) 110 | .to.be.equal('9eca7128409f609b2a72fc24985645665bbb99152b4b14261c3c3c93fb17cf54'); 111 | }); 112 | }); 113 | 114 | it('should be work well for medical-fhir observation', () => { 115 | const filePath = path.resolve(__dirname, './samples/observation_sample.json'); 116 | return healthData.hashDataFromFile( 117 | filePath, 118 | 'medical-fhir', 119 | 'observation', 120 | ).then((hash) => { 121 | return expect(hash) 122 | .to.be.equal('eb36d0606ff84bba5ae84e2af0f2197b2ff4272c3d22c46ffa27ca17851cea7f'); 123 | }); 124 | }); 125 | 126 | it('should be work well for pghd txt format', () => { 127 | const filePath = path.resolve(__dirname, './samples/pghd.txt'); 128 | return healthData.hashDataFromFile( 129 | filePath, 130 | 'pghd', 131 | null, 132 | ).then((hash) => { 133 | return expect(hash) 134 | .to.be.equal('1d805ad9f40442aaf7376a7d11287a4a669a8ca1c70756fd0329aef00b139b83'); 135 | }); 136 | }); 137 | 138 | it('should be work well for pghd json format', () => { 139 | const filePath = path.resolve(__dirname, './samples/pghd.json'); 140 | return healthData.hashDataFromFile( 141 | filePath, 142 | 'pghd', 143 | null, 144 | ).then((hash) => { 145 | return expect(hash) 146 | .to.be.equal('559e6fd3a29685fbe1e27b55d716ea78372fd9ce4583e07ae92dce6264ee83c6'); 147 | }); 148 | }); 149 | 150 | it('should be work well for dicom', () => { 151 | const filePath = path.resolve(__dirname, './samples/ultrasound_sample.dcm'); 152 | return healthData.hashDataFromFile( 153 | filePath, 154 | 'dicom', 155 | null, 156 | ).then((hash) => { 157 | return expect(hash) 158 | .to.be.equal('11c958fa583e0a3a1184bc318d022936ce347146a47860f4f8b59307c5321aa0'); 159 | }); 160 | }); 161 | }); 162 | }); 163 | -------------------------------------------------------------------------------- /docs/panaceajs-utils.rst: -------------------------------------------------------------------------------- 1 | .. _utils:'' 2 | 3 | .. include:: include_announcement.rst 4 | 5 | =========== 6 | panaceajs.utils 7 | =========== 8 | 9 | The ``panaceajs.utils`` provides utility functions for panaceajs. 10 | 11 | To use this package standalone use: 12 | 13 | .. code-block:: javascript 14 | 15 | var Utils = require('@medibloc/panacea-js').utils; 16 | // 17 | // Instead, you can import from panaceajs like below. 18 | // 19 | // var Panaceajs = require('@medibloc/panacea-js'); 20 | // var panaceajs = Panaceajs.init(['http://localhost:9921']); 21 | // var Utils = panaceajs.utils; 22 | 23 | --------------------------------------------------------------------------- 24 | 25 | genHexBuf 26 | ========= 27 | 28 | .. code-block:: javascript 29 | 30 | Utils.genHexBuf(str, bytesLen); 31 | 32 | Returns ``Buffer`` or ``Uint8Array`` from a string with exact length in bytes. 33 | 34 | ---------- 35 | Parameters 36 | ---------- 37 | 38 | 1. ``str`` - ``String`` : The string to generate a buffer. 39 | 2. ``bytesLen`` - ``Number`` : The target length in bytes. 40 | 41 | ------- 42 | Returns 43 | ------- 44 | 45 | ``Buffer|Uint8Array`` - The result Buffer or Uint8Array. 46 | 47 | ------- 48 | Example 49 | ------- 50 | 51 | .. code-block:: javascript 52 | 53 | Utils.genHexBuf('12ab', 5); 54 | > 55 | 56 | --------------------------------------------------------------------------- 57 | 58 | .. _utils-isAddress: 59 | 60 | isAddress 61 | ========= 62 | 63 | .. code-block:: javascript 64 | 65 | Utils.isAddress(pubKey); 66 | 67 | To validate the public key, you can use ``Utils.isAddress(pubKey)``. 68 | 69 | .. note:: MediBloc use public key as an address. 70 | 71 | ---------- 72 | Parameters 73 | ---------- 74 | 75 | ``pubKey`` - ``String`` : The public key to validate. 76 | 77 | ------- 78 | Returns 79 | ------- 80 | 81 | ``Boolean`` - It is ``true`` if the public key is valid. 82 | 83 | ------- 84 | Example 85 | ------- 86 | 87 | .. code-block:: javascript 88 | 89 | Utils.isAddress('037d91596727bc522553510b34815f382c2060cbb776f2765deafb48ae528d324b'); 90 | > true 91 | 92 | --------------------------------------------------------------------------- 93 | 94 | isHexadecimal 95 | ============= 96 | 97 | .. code-block:: javascript 98 | 99 | Utils.isHexadecimal(string); 100 | 101 | To check the type of the string, you can use ``Utils.isHexadecimal(string)``. 102 | 103 | ---------- 104 | Parameters 105 | ---------- 106 | 107 | ``string`` - ``String`` : The string to be validated. 108 | 109 | ------- 110 | Returns 111 | ------- 112 | 113 | ``Boolean`` - It is ``true`` if the string is in hexadecimal format. 114 | 115 | ------- 116 | Example 117 | ------- 118 | 119 | .. code-block:: javascript 120 | 121 | Utils.isHexadecimal('1234567890abcdef'); 122 | > true 123 | 124 | --------------------------------------------------------------------------- 125 | 126 | padLeftWithZero 127 | =============== 128 | 129 | .. code-block:: javascript 130 | 131 | Utils.padLeftWithZero(str, len); 132 | 133 | Adds a ``'0'`` padding on the left of a string. 134 | 135 | ---------- 136 | Parameters 137 | ---------- 138 | 139 | 1. ``str`` - ``String`` : The string to add padding on the left. 140 | 2. ``len`` - ``Number`` : The total length of the string should have. 141 | 142 | ------- 143 | Returns 144 | ------- 145 | 146 | ``String`` - The padded string. 147 | 148 | ------- 149 | Example 150 | ------- 151 | 152 | .. code-block:: javascript 153 | 154 | Utils.padLeftWithZero('12ab', 10); 155 | > '00000012ab' 156 | 157 | --------------------------------------------------------------------------- 158 | 159 | randomHex 160 | ========= 161 | 162 | .. code-block:: javascript 163 | 164 | Utils.randomHex(length); 165 | 166 | To get a random seed number, you can use ``Utils.randomHex(length)``. 167 | 168 | ---------- 169 | Parameters 170 | ---------- 171 | 172 | ``length`` - ``Number`` : (optional) The byte size of a random seed number. If not given, 16 is used. 173 | 174 | ------- 175 | Returns 176 | ------- 177 | 178 | ``String`` - The random number in hexadecimal format. 179 | 180 | ------- 181 | Example 182 | ------- 183 | 184 | .. code-block:: javascript 185 | 186 | Utils.randomHex(); 187 | > 'baab6c02ce89592e03b8f9bbea8eb553' 188 | 189 | --------------------------------------------------------------------------- 190 | 191 | sha3 192 | ==== 193 | 194 | .. code-block:: javascript 195 | 196 | Utils.sha3(msg); 197 | 198 | To hash messages, you can use ``Utils.sha3(msg)``. This function uses SHA3_256 algorithm and returns 256bit hexadecimal string. 199 | 200 | ---------- 201 | Parameters 202 | ---------- 203 | 204 | ``msg`` - ``String|Object|Number`` : The message is stringified. 205 | 206 | ------- 207 | Returns 208 | ------- 209 | 210 | ``String`` - The hash string in hexadecimal format. 211 | 212 | ------- 213 | Example 214 | ------- 215 | 216 | .. code-block:: javascript 217 | 218 | Utils.sha3('Hello MediBloc!!!'); 219 | > '25cd0631574c642502446ace0c9c46811f1404e39d6d892771b346724851dd7e' 220 | 221 | --------------------------------------------------------------------------- 222 | 223 | sha3Stream 224 | ========== 225 | 226 | .. code-block:: javascript 227 | 228 | Utils.sha3Stream(stream); 229 | 230 | To hash stream, you can use ``Utils.sha3Stream(stream)``. This function uses SHA3_256 algorithm and returns 256bit hexadecimal string. 231 | 232 | ---------- 233 | Parameters 234 | ---------- 235 | 236 | ``stream`` - ``Stream`` : The readable stream. 237 | 238 | ------- 239 | Returns 240 | ------- 241 | 242 | ``String`` - The hash string in hexadecimal format. 243 | 244 | ------- 245 | Example 246 | ------- 247 | 248 | .. code-block:: javascript 249 | 250 | Utils.sha3Stream(stream); // some readable stream 251 | > '8a1fb1154b917c9e3df4370008e0bf34c6de6babb1592225371731a71a9b2e13' 252 | -------------------------------------------------------------------------------- /test/cryptography/encrypt.js: -------------------------------------------------------------------------------- 1 | import chai, { expect } from 'chai'; 2 | import cryptography from 'cryptography'; 3 | import chaiHexString from 'test/helpers/chaiHexString'; 4 | 5 | chai.use(chaiHexString); 6 | 7 | // encrypt and decrypt data 8 | describe('#encryptData / #decryptData', () => { 9 | const accessKey = 'hello medibloc'; 10 | const fakeAccessKey = 'hello medibloc!'; 11 | const data = 'Hello medibloc. It\'s good time to surf'; 12 | const testData = 'Hello medibloc. It\'s good time to surf!'; 13 | describe('should make encrypted message', () => { 14 | const encryptedData = cryptography.encryptData(accessKey, data); 15 | const encryptedTestData = cryptography.encryptData(accessKey, testData); 16 | it('Encrypted data should be unique', () => { 17 | expect(encryptedData).not.to.be.equal(data); 18 | expect(encryptedData).not.to.be.equal(encryptedTestData); 19 | }); 20 | }); 21 | describe('can be decrypted with right access key', () => { 22 | const encryptedData = cryptography.encryptData(accessKey, data); 23 | it('Access key can decrypt message', () => { 24 | const decryptedMsg = cryptography.decryptData(accessKey, encryptedData); 25 | expect(decryptedMsg).to.be.equal(data); 26 | }); 27 | it('encrypted message format should have delimiter ":"', () => { 28 | try { 29 | cryptography.decryptData(fakeAccessKey, 'hello medibloc'); 30 | } catch (err) { 31 | expect(err.message).to.equal('Invalid encrypted data format'); 32 | } 33 | }); 34 | it('Only right access key should work', () => { 35 | const mismatchedData = cryptography.decryptData(fakeAccessKey, encryptedData); 36 | expect(mismatchedData).not.to.equal(data); 37 | }); 38 | }); 39 | }); 40 | 41 | // encrypt and decrypt key 42 | describe('#key encryption', () => { 43 | const password = 'medibloc12345^&*()'; 44 | const privKey = '47a9bf274c67c6d2e79baf830d96c4595d2ba46b19155c873b61f39c38218a20'; 45 | 46 | it('should be done with pbkdf2 algorithm', () => { 47 | const options = { 48 | iv: Buffer.from('eeed284aa6811376b1fc43bd0d2649f8', 'hex'), 49 | kdf: 'pbkdf2', 50 | salt: Buffer.from('7f64efa7aae09d54655544b351dd1948c41237f8be9d795a615f3215413b470e', 'hex'), 51 | }; 52 | const expectedCrypto = { 53 | ciphertext: '4daa96ce3c87a52fb4ee8022e6b7f2ee0211975124de7d72ada17a99818a405b', 54 | cipherparams: { iv: 'eeed284aa6811376b1fc43bd0d2649f8' }, 55 | cipher: 'aes-128-ctr', 56 | kdf: 'pbkdf2', 57 | kdfparams: { 58 | dklen: 32, 59 | salt: '7f64efa7aae09d54655544b351dd1948c41237f8be9d795a615f3215413b470e', 60 | c: 262144, 61 | prf: 'hmac-sha256', 62 | }, 63 | mac: '430cc755203ce23caca3b63e4e58b331b7cdb754da8cdde517267072c066ec6c', 64 | }; 65 | const encryptedPrivKey = cryptography.encryptKey(password, privKey, options); 66 | expect(encryptedPrivKey).to.be.a('object') 67 | .to.have.property('crypto') 68 | .to.eql(expectedCrypto); 69 | }); 70 | it('should be done with scrypt algorithm', () => { 71 | const options = { 72 | iv: Buffer.from('eeed284aa6811376b1fc43bd0d2649f8', 'hex'), 73 | kdf: 'scrypt', 74 | salt: Buffer.from('7f64efa7aae09d54655544b351dd1948c41237f8be9d795a615f3215413b470e', 'hex'), 75 | }; 76 | const expectedCrypto = { 77 | ciphertext: '9883a3936282a816d75591bbf330c0dc644b91e8619e892ccbb29a25a4063cad', 78 | cipherparams: { iv: 'eeed284aa6811376b1fc43bd0d2649f8' }, 79 | cipher: 'aes-128-ctr', 80 | kdf: 'scrypt', 81 | kdfparams: { 82 | dklen: 32, 83 | salt: '7f64efa7aae09d54655544b351dd1948c41237f8be9d795a615f3215413b470e', 84 | n: 8192, 85 | r: 8, 86 | p: 1, 87 | }, 88 | mac: '51a2041e8ea6e44d31e8a7050b5dac0f4e56391a156cd60d7d7ff0d5ae37f76b', 89 | }; 90 | const encryptedPrivKey = cryptography.encryptKey(password, privKey, options); 91 | expect(encryptedPrivKey).to.be.a('object') 92 | .to.have.property('crypto') 93 | .to.eql(expectedCrypto); 94 | }); 95 | }); 96 | 97 | // encrypt and decrypt key 98 | describe('#key decryption', () => { 99 | const password = 'medibloc12345^&*()'; 100 | const expectedPrivKey = '47a9bf274c67c6d2e79baf830d96c4595d2ba46b19155c873b61f39c38218a20'; 101 | 102 | it('should be done with pbkdf2 algorithm', () => { 103 | const encryptedPrivKey = { 104 | version: 3, 105 | id: 'd3f4a51b-575b-4b25-abcd-a1160cb1acee', 106 | address: '03b7f30a8f815a4d31c711ce374b9bd491fcb354dc6d6f9d9b8fc5ed1194703edb', 107 | crypto: { 108 | ciphertext: '4daa96ce3c87a52fb4ee8022e6b7f2ee0211975124de7d72ada17a99818a405b', 109 | cipherparams: { iv: 'eeed284aa6811376b1fc43bd0d2649f8' }, 110 | cipher: 'aes-128-ctr', 111 | kdf: 'pbkdf2', 112 | kdfparams: { 113 | dklen: 32, 114 | salt: '7f64efa7aae09d54655544b351dd1948c41237f8be9d795a615f3215413b470e', 115 | c: 262144, 116 | prf: 'hmac-sha256', 117 | }, 118 | mac: '430cc755203ce23caca3b63e4e58b331b7cdb754da8cdde517267072c066ec6c', 119 | }, 120 | }; 121 | expect(cryptography.decryptKey(password, encryptedPrivKey)).to.equal(expectedPrivKey); 122 | }); 123 | it('should be done with scrypt algorithm', () => { 124 | const encryptedPrivKey = { 125 | version: 3, 126 | id: 'dae02e97-06d2-45bf-b2a1-7f342cf5deff', 127 | address: '03b7f30a8f815a4d31c711ce374b9bd491fcb354dc6d6f9d9b8fc5ed1194703edb', 128 | crypto: { 129 | ciphertext: '9883a3936282a816d75591bbf330c0dc644b91e8619e892ccbb29a25a4063cad', 130 | cipherparams: { iv: 'eeed284aa6811376b1fc43bd0d2649f8' }, 131 | cipher: 'aes-128-ctr', 132 | kdf: 'scrypt', 133 | kdfparams: { 134 | dklen: 32, 135 | salt: '7f64efa7aae09d54655544b351dd1948c41237f8be9d795a615f3215413b470e', 136 | n: 8192, 137 | r: 8, 138 | p: 1, 139 | }, 140 | mac: '51a2041e8ea6e44d31e8a7050b5dac0f4e56391a156cd60d7d7ff0d5ae37f76b', 141 | }, 142 | }; 143 | expect(cryptography.decryptKey(password, encryptedPrivKey)).to.equal(expectedPrivKey); 144 | }); 145 | it('should be done with scrypt algorithm with previous format', () => { 146 | const encryptedPrivKey = { 147 | version: 3, 148 | id: '25ce59ce-75fd-4fd8-8b21-15f60893971a', 149 | address: '03b7f30a8f815a4d31c711ce374b9bd491fcb354dc6d6f9d9b8fc5ed1194703edb', 150 | crypto: { 151 | ciphertext: 'eb1d7d8d4c835cf304ad080f9d9060b75c570ce119eded98c0b7508fff1383b46e2eb20b41c0aafd9afcea50b1e41e89a9598d13af976e64220167832f70d0db', 152 | cipherparams: { iv: 'eeed284aa6811376b1fc43bd0d2649f8' }, 153 | cipher: 'aes-128-ctr', 154 | kdf: 'scrypt', 155 | kdfparams: { 156 | dklen: 32, 157 | salt: '7f64efa7aae09d54655544b351dd1948c41237f8be9d795a615f3215413b470e', 158 | n: 8192, 159 | r: 8, 160 | p: 1, 161 | }, 162 | mac: '0260b761d2661388f449c0b162c56c919d3802e6c04e25d0ef0fb5d9e7564b92', 163 | }, 164 | }; 165 | 166 | expect(cryptography.decryptKey(password, encryptedPrivKey)).to.equal(expectedPrivKey); 167 | }); 168 | }); 169 | -------------------------------------------------------------------------------- /docs/panaceajs-healthData.rst: -------------------------------------------------------------------------------- 1 | .. _healthData: 2 | 3 | .. include:: include_announcement.rst 4 | 5 | ================ 6 | panaceajs.healthData 7 | ================ 8 | 9 | The ``panaceajs.healthData`` object helps to encode and decode the health data as :ref:`MHD format `. 10 | 11 | .. code-block:: javascript 12 | 13 | var HealthData = require('@medibloc/panacea-js').healthData; 14 | // 15 | // Instead, you can import from panaceajs like below. 16 | // 17 | // var Panaceajs = require('@medibloc/panacea-js'); 18 | // var panaceajs = Panaceajs.init(['http://localhost:9921']); 19 | // var HealthData = panaceajs.healthData; 20 | 21 | .. include:: include_blockchain_note.rst 22 | 23 | --------------------------------------------------------------------------- 24 | 25 | .. _mhd: 26 | 27 | MediBloc Health Data(MHD) Format 28 | ================================ 29 | 30 | ======== ========== =============================================== 31 | Offset Bytes Description 32 | -------- ---------- ----------------------------------------------- 33 | 0 4 magic number(=0x004d4844) 34 | 4 2 protocol version 35 | 6 2 | type code 36 | | ( unknown: 0, medical-fhir: 1, 37 | | claim-fhir: 2, dicom: 3, ... ) 38 | 8 2 | subtype code of each type 39 | | ( for medical-fhir type, 40 | | null: 0, patient: 1, 41 | | observation: 2, ... ) 42 | 10 32 hash of the encoded health data 43 | 42 6 encoded health data size(m) 44 | 48 m encoded health data 45 | ======== ========== =============================================== 46 | 47 | We defined the MediBloc Health Data(MHD) format like above. 48 | More types of health data and its subtype will be supported. 49 | 50 | User should upload the hash of the encoded health data to the MediBloc blockchain. 51 | By storing or transferring the health data as MHD type, it is easy to handle the health data with the blockchain. 52 | 53 | .. warning:: 54 | This format can be changed. 55 | 56 | --------------------------------------------------------------------------- 57 | 58 | decodeData 59 | ========== 60 | 61 | .. code-block:: javascript 62 | 63 | HealthData.decodeData(data) 64 | 65 | Returns decoded health data. 66 | 67 | Parameters 68 | ---------- 69 | 70 | ``data`` - ``Buffer|Uint8Array``: The MHD format health data. 71 | 72 | Returns 73 | ------- 74 | 75 | ``Promise`` returns ``Object`` - The JSON object of the health data. 76 | 77 | Example 78 | ------- 79 | 80 | .. code-block:: javascript 81 | 82 | var data = fs.readFileSync('/file/path'); 83 | console.log(data); 84 | > 85 | 86 | HealthData.decodeData(data) 87 | .then(console.log); 88 | > { 89 | status: 'final', 90 | category: [ { coding: [Array] } ], 91 | code: { coding: [ [Object], [Object] ] }, 92 | resourceType: 'Observation', 93 | effectiveDateTime: '2017-05-15', 94 | id: 'f101', 95 | context: { reference: 'Encounter/f100' }, 96 | subject: { reference: 'Patient/f100' }, 97 | valueQuantity: 98 | { code: 'kg', 99 | unit: 'kg', 100 | value: 78, 101 | system: 'http://unitsofmeasure.org' } } 102 | 103 | --------------------------------------------------------------------------- 104 | 105 | 106 | decodeDataFromFile 107 | ================== 108 | 109 | .. code-block:: javascript 110 | 111 | HealthData.decodeDataFromFile(filePath) 112 | 113 | Returns decoded health data from the file path. 114 | 115 | Parameters 116 | ---------- 117 | 118 | ``filePath`` - ``String``: The path of the MHD format file to read. 119 | 120 | Returns 121 | ------- 122 | 123 | ``Promise`` returns ``Object`` - The JSON object of the health data. 124 | 125 | Example 126 | ------- 127 | 128 | .. code-block:: javascript 129 | 130 | HealthData.decodeDataFromFile('/file/path') 131 | .then(console.log); 132 | > { 133 | status: 'final', 134 | category: [ { coding: [Array] } ], 135 | code: { coding: [ [Object], [Object] ] }, 136 | resourceType: 'Observation', 137 | effectiveDateTime: '2017-05-15', 138 | id: 'f101', 139 | context: { reference: 'Encounter/f100' }, 140 | subject: { reference: 'Patient/f100' }, 141 | valueQuantity: 142 | { code: 'kg', 143 | unit: 'kg', 144 | value: 78, 145 | system: 'http://unitsofmeasure.org' } } 146 | 147 | --------------------------------------------------------------------------- 148 | 149 | 150 | encodeData 151 | ========== 152 | 153 | .. code-block:: javascript 154 | 155 | HealthData.encodeData(data, type, subType) 156 | 157 | Returns encoded ``Buffer|Uint8Array`` object as MHD format of the health data. 158 | 159 | 160 | Parameters 161 | ---------- 162 | 163 | 1. ``data`` - ``Object|Uint8Array|Buffer``: The health data to encode. 164 | 2. ``type`` - ``String``: The type of the health data. 165 | 3. ``subType`` - ``String``:(optional) The subtype of the health data. 166 | 167 | Returns 168 | ------- 169 | 170 | ``Promise`` returns ``Buffer|Uint8Array`` - The MHD format object of the health data. 171 | 172 | Example 173 | ------- 174 | 175 | .. code-block:: javascript 176 | 177 | var data = { 178 | resourceType: 'Observation', 179 | id: 'f101', 180 | status: 'final', 181 | category: [ 182 | { 183 | coding: [ 184 | { 185 | system: 'http://hl7.org/fhir/observation-category', 186 | code: 'vital-signs', 187 | display: 'Vital Signs', 188 | }, 189 | ], 190 | }, 191 | ], 192 | ... 193 | }; 194 | 195 | HealthData.encodeData(data, 'medical-fhir', 'observation') 196 | .then(console.log); 197 | > { 198 | 199 | } 200 | 201 | --------------------------------------------------------------------------- 202 | 203 | 204 | encodeDataFromFile 205 | ================== 206 | 207 | .. code-block:: javascript 208 | 209 | HealthData.encodeDataFromFile(filePath, type, subType) 210 | 211 | Returns encoded ``Buffer|Uint8Array`` object as MHD format object of the health data reading from the file path. 212 | 213 | 214 | Parameters 215 | ---------- 216 | 217 | 1. ``filePath`` - ``String``: The path of the file to read. 218 | 2. ``type`` - ``String``: The type of the health data. 219 | 3. ``subType`` - ``String``:(optional) The subtype of the health data. 220 | 221 | 222 | Returns 223 | ------- 224 | 225 | ``Promise`` returns ``Buffer|Uint8Array`` - The MHD format object of the health data. 226 | 227 | Example 228 | ------- 229 | 230 | .. code-block:: javascript 231 | 232 | HealthData.encodeDataFromFile('/file/path', 'medical-fhir', 'observation') 233 | .then(console.log); 234 | > { 235 | 236 | } 237 | 238 | --------------------------------------------------------------------------- 239 | 240 | 241 | hashData 242 | ======== 243 | 244 | .. code-block:: javascript 245 | 246 | HealthData.hashData(data, type, subType) 247 | 248 | Returns the hash ``String`` of the health data. 249 | 250 | 251 | Parameters 252 | ---------- 253 | 254 | 1. ``data`` - ``Object|Uint8Array|Buffer``: The health data to encode. 255 | 2. ``type`` - ``String``: The type of the health data. 256 | 3. ``subType`` - ``String``:(optional) The subtype of the health data. 257 | 258 | 259 | Returns 260 | ------- 261 | 262 | ``Promise`` returns ``String`` - The hash of the health data. 263 | 264 | Example 265 | ------- 266 | 267 | .. code-block:: javascript 268 | 269 | var data = { 270 | resourceType: 'Observation', 271 | id: 'f101', 272 | status: 'final', 273 | category: [ 274 | { 275 | coding: [ 276 | { 277 | system: 'http://hl7.org/fhir/observation-category', 278 | code: 'vital-signs', 279 | display: 'Vital Signs', 280 | }, 281 | ], 282 | }, 283 | ], 284 | ... 285 | }; 286 | 287 | HealthData.hashData(data, 'medical-fhir', 'observation') 288 | .then(console.log); 289 | > { 290 | 'eb36d0606ff84bba5ae84e2af0f2197b2ff4272c3d22c46ffa27ca17851cea7f' 291 | } 292 | 293 | --------------------------------------------------------------------------- 294 | 295 | hashDataFromFile 296 | ================ 297 | 298 | .. code-block:: javascript 299 | 300 | HealthData.hashDataFromFile(filePath, type, subType) 301 | 302 | Returns the hash ``String`` of the health data reading from the file path. 303 | 304 | 305 | Parameters 306 | ---------- 307 | 308 | 1. ``filePath`` - ``String``: The path of the file to read. 309 | 2. ``type`` - ``String``: The type of the health data. 310 | 3. ``subType`` - ``String``:(optional) The subtype of the health data. 311 | 312 | 313 | Returns 314 | ------- 315 | 316 | ``Promise`` returns ``String`` - The hash of the health data. 317 | 318 | Example 319 | ------- 320 | 321 | .. code-block:: javascript 322 | 323 | HealthData.hashDataFromFile('/file/path', 'medical-fhir', 'observation') 324 | .then(console.log); 325 | > { 326 | 'eb36d0606ff84bba5ae84e2af0f2197b2ff4272c3d22c46ffa27ca17851cea7f' 327 | } 328 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /docs/panaceajs-account.rst: -------------------------------------------------------------------------------- 1 | .. _account: 2 | 3 | .. include:: include_announcement.rst 4 | 5 | =================== 6 | panaceajs.local.Account 7 | =================== 8 | 9 | The ``panaceajs.local.Account`` contains functions to generate MediBloc accounts, which contain encrypted private key and public key pair and can induce public key from the private key. 10 | 11 | To use this package in a standalone use: 12 | 13 | .. code-block:: javascript 14 | 15 | var Account = require('@medibloc/panacea-js').local.Account; 16 | // 17 | // Instead, you can import from panaceajs like below. 18 | // 19 | // var Panaceajs = require('@medibloc/panacea-js'); 20 | // var panaceajs = Panaceajs.init(['http://localhost:9921']); 21 | // var Account = panaceajs.local.Account; 22 | 23 | --------------------------------------------------------------------------- 24 | 25 | .. _account-create: 26 | 27 | new Account 28 | =========== 29 | 30 | .. code-block:: javascript 31 | 32 | new Account(passphrase, encryptedPrivateKey, pubKey); 33 | 34 | To generate account, you can use ``panaceajs.local.Account()``. Basically, account is just a pair of private and public key that has several functions described as below. 35 | 36 | .. note:: MediBloc uses public key as an address. 37 | 38 | ---------- 39 | Parameters 40 | ---------- 41 | 42 | 1. ``passphrase`` - ``String`` :(optional) If ``encryptedPrivateKey`` is not given, passphrase works as a key to encrypt private key. If ``encryptedPrivateKey`` is given, passphrase works as a key to decrypt encryptedPrivateKey and it must be used in encryption of the ``encryptedPrivateKey``. If not given, passphrase is set with an empty string. 43 | 2. ``encryptedPrivateKey`` - ``String`` :(optional) Restore account is matched with the given encrypted private key. If not given, it will generate a new keypair. 44 | 3. ``pubKey`` - ``String``(optional) Restore account is matched with the given public key. If not given, it will be decrypted from the given passphrase and the given encryptedPrivateKey. 45 | 46 | .. note:: If ``passphrase`` does not match with ``encryptedPrivateKey``, it will return a different private key. 47 | 48 | ------- 49 | Returns 50 | ------- 51 | 52 | ``Object`` - The account object with the following structure: 53 | 54 | - ``pubKey`` - ``String``: The account's public key. 55 | - ``encryptedPrivKey`` - ``String``: The account's encrypted private key. This should be carefully shared or stored. 56 | - And other following functions... 57 | 58 | ------- 59 | Example 60 | ------- 61 | 62 | .. code-block:: javascript 63 | 64 | var accountNoPassphrase = new Account(); 65 | console.log(accountNoPassphrase); 66 | > Account { 67 | encryptedPrivKey: { 68 | version: 3, 69 | id: '6402eabe-db3f-497e-8d22-134d3690c349', 70 | address: '02ed468d1c8e4ee91b889b3e8fe79cd024df5ef4087c4ab1141d365a7b8d218ca4', 71 | ... 72 | }, 73 | pubKey: '02ed468d1c8e4ee91b889b3e8fe79cd024df5ef4087c4ab1141d365a7b8d218ca4', 74 | ... 75 | } 76 | 77 | var account = new Account('123456789abcdeABCDE!@#'); 78 | console.log(account); 79 | > Account { 80 | encryptedPrivKey: { 81 | version: 3, 82 | id: 'ca05cb03-7f84-4251-9c8c-e468fda47f0f', 83 | address: '0387e6dd9576a9bc792658bcedcb257311739c88b8a3ca68eef980316bb73a45d9', 84 | ... 85 | }, 86 | pubKey: '0387e6dd9576a9bc792658bcedcb257311739c88b8a3ca68eef980316bb73a45d9', 87 | ... 88 | } 89 | 90 | var account1 = new Account('123456789abcdeABCDE!@#', account.encryptedPrivKey); 91 | console.log(account1); 92 | > // same with the previous result 93 | 94 | var account2 = new Account('', account.encryptedPrivKey, account.pubKey); 95 | console.log(account2); 96 | > // same with the previous result 97 | 98 | .. note:: SDK doesn't hold or share unencrypted private key. Account object holds encrypted private key and only the right passphrase can retrieve the unencrypted private key. 99 | 100 | --------------------------------------------------------------------------- 101 | 102 | createCertificate 103 | ================= 104 | 105 | .. code-block:: javascript 106 | 107 | var account = new Account(passphrase, encryptedPrivateKey); 108 | account.createCertificate(expireDate, issuer, issueDate, passphrase); 109 | 110 | To create the certificate of the account, use ``account.createCertificate(expireDate, issuer, issueDate, passphrase)``. 111 | 112 | ---------- 113 | Parameters 114 | ---------- 115 | 116 | 117 | 1. ``expireDate`` - ``Number`` : The unix timestamp when certificate is expired. 118 | 2. ``issuer`` - ``String`` : The issuer's url to check certificate authenticity. 119 | 3. ``issueDate`` - ``Number`` : The unix timestamp when issuing certificate. 120 | 4. ``passphrase`` - ``String`` :(optional) The passphrase to decrypt encrypted private key. If not given, empty string is used to decrypt. 121 | 122 | .. note:: Account.createCertificate doesn't return anything but assign the certificate object to the account. After signing, ``account.cert`` is changed from ``Null`` to ``Object``. 123 | 124 | ------- 125 | Example 126 | ------- 127 | 128 | .. code-block:: javascript 129 | 130 | var owner = new Account(); 131 | owner.createCertificate({ 132 | expireDate: Date.now() + (365 * 24 * 60 * 60 * 1000), 133 | issuer: 'https://medibloc.org', 134 | issueDate: Date.now(), 135 | passphrase: '', 136 | }); 137 | console.log(owner.cert); 138 | > { 139 | expireDate: 1558759043199, 140 | issuer: 'https://medibloc.org', 141 | issueDate: 1527223043199, 142 | pubKey: '020505d5ce655f7651eddfc6ee8bc96a78c40a622c5e28b1b8dfe1cf0f3af6c448', 143 | signature: '1d7b003afb947bcb6e8f27f1366a34d27f473c398e98c7cc36a8720dbfda064e03cfd35cf352057a23194da874afbe9a00d37a20efec8d9ae39c43f943ed14de01' 144 | } 145 | 146 | 147 | --------------------------------------------------------------------------- 148 | 149 | getDecryptedPrivateKey 150 | ====================== 151 | 152 | .. code-block:: javascript 153 | 154 | var account = new Account(passphrase, encryptedPrivateKey); 155 | account.getDecryptedPrivateKey(passphrase); 156 | 157 | To decrypt encrypted private key with the passphrase from the ``account`` object, you can use ``account.getDecryptedPrivateKey(passphrase)``. 158 | 159 | ---------- 160 | Parameters 161 | ---------- 162 | 163 | ``passphrase`` - ``String`` :(optional) Passphrase is used to decrypt encrypted private key. If not given, empty string is used to decrypt. 164 | 165 | .. note:: If ``passphrase`` does not match with ``encryptedPrivateKey``, it will return a different private key. 166 | 167 | ------- 168 | Returns 169 | ------- 170 | 171 | ``String`` - Decrypted private key in hexadecimal format. 172 | 173 | ------- 174 | Example 175 | ------- 176 | 177 | .. code-block:: javascript 178 | 179 | var account = new Account('123456789abcdeABCDE!@#'); 180 | account.getDecryptedPrivateKey('123456789abcdeABCDE!@#'); 181 | > '960d2ea9a19b2b939b2ecbdbba75ffb50aafa0b63a73cd1b614cb53c50482d26' 182 | 183 | --------------------------------------------------------------------------- 184 | 185 | signTx 186 | ====== 187 | 188 | .. code-block:: javascript 189 | 190 | var account = new Account(passphrase, encryptedPrivateKey); 191 | account.signTx(tx, passphrase); 192 | 193 | To sign a transaction with the private key, you can use ``account.signTx(tx, passphrase)``. It assigns signature string to ``tx.sign``. 194 | 195 | ---------- 196 | Parameters 197 | ---------- 198 | 199 | 1. ``tx`` - ``Object`` : Transaction object created from one of the :ref:`transaction creation functions `. 200 | 2. ``passphrase`` - ``String`` :(optional) The passphrase to decrypt encrypted private key. If not given, empty string is used to decrypt the encrypted private key. 201 | 202 | .. note:: account.signTx doesn't return anything but assign a signature string to the transaction object. After signing, ``transaction.sign`` is changed from ``null`` to ``String``. 203 | 204 | ------- 205 | Example 206 | ------- 207 | 208 | .. code-block:: javascript 209 | 210 | var owner = new Account(); 211 | var transactionData = { 212 | from: owner.pubKey, 213 | to: '0266e30b34c9b377c9699c026872429a0fa582ac802759a3f35f9e90b352b8d932', 214 | value: '5', 215 | nonce: 3 216 | }; 217 | var transaction = Transaction.valueTransferTx(transactionData); 218 | owner.signTx(transaction); 219 | console.log(transaction); 220 | > { 221 | rawTx: {...}, 222 | hash: '15be7e844e19ecdbad46894bf310e7c15bb315837baf4aac82991d0c531b02d8', 223 | sign: '882c24751521bae53bff1673b896b3d0cce2b81a03fea9563323975b79955cbe134744cbd21913955093e60c8d56d3884d7863db88b5393135f667f510fcceb200' 224 | } 225 | 226 | --------------------------------------------------------------------------- 227 | 228 | signTxAsPayer 229 | ============= 230 | 231 | .. code-block:: javascript 232 | 233 | var account = new Account(passphrase, encryptedPrivateKey); 234 | account.signTxAsPayer(tx, passphrase); 235 | 236 | To sign a transaction as payer with the private key, you can use ``account.signTxAsPayer(tx, passphrase)``. It assigns signature string to ``tx.payerSign``. 237 | 238 | ---------- 239 | Parameters 240 | ---------- 241 | 242 | 1. ``tx`` - ``Object`` : Transaction object created from one of the :ref:`transaction creation functions ` and signed by a requester. 243 | 2. ``passphrase`` - ``String`` :(optional) The passphrase to decrypt encrypted private key. If not given, empty string is used to decrypt the encrypted private key. 244 | 245 | .. note:: account.signTxAsPayer doesn't return anything but assign a signature string to the transaction object. After signing, ``transaction.payerSign`` is set as ``String``. 246 | 247 | ------- 248 | Example 249 | ------- 250 | 251 | .. code-block:: javascript 252 | 253 | var requester = new Account('MediBloc1!'); 254 | var payer = new Account('MediBloc2@'); 255 | var transactionData = { 256 | from: requester.pubKey, 257 | to: '0266e30b34c9b377c9699c026872429a0fa582ac802759a3f35f9e90b352b8d932', 258 | value: '5', 259 | nonce: 3 260 | }; 261 | var transaction = Transaction.valueTransferTx(transactionData); 262 | requester.signTx(transaction, 'MediBloc1!'); 263 | payer.signTxAsPayer(transaction, 'MediBloc2@'); 264 | console.log(transaction); 265 | > { 266 | rawTx: {...}, 267 | hash: 'd04bd6cb8eef9f59e9cab7fcb253303d003ce34f30bd8e0f59c09fba5281c303', 268 | sign: '5351c2e3375570ab15c4f1ef36b4d854516a368a4e0ad3e1c26d32df854083bb3bf9cc188fdc242484183edff5aeb8615cda7c1ebb4f6948ec00641bd9bfa06000', 269 | payerSign: '0b0b28624ab007538c7044624f7a1f90c5e7b215118e8894288378c8895f2a585d10087d4be3d20a22bf381360fb70a5da2c4da95dc8430218a3d0ec3acfc11001', 270 | } 271 | 272 | --------------------------------------------------------------------------- 273 | 274 | signDataPayload 275 | =============== 276 | 277 | .. code-block:: javascript 278 | 279 | var account = new Account(passphrase, encryptedPrivateKey); 280 | account.signDataPayload(dataPayload, passphrase); 281 | 282 | To sign a data payload with the private key, you can use ``account.signDataPayload(dataPayload, passphrase)``. It assigns signature string to ``dataPayload.sign``. 283 | 284 | ---------- 285 | Parameters 286 | ---------- 287 | 288 | 1. ``dataPayload`` - ``Object`` : data payload object: 289 | 290 | - ``hash`` - ``String``: The hash string of the data payload. 291 | 292 | 2. ``passphrase`` - ``String``:(optional) The passphrase to decrypt encrypted private key. If not given, empty string is used to decrypt. 293 | 294 | .. note:: account.signDataPayload doesn't return anything but assign the signature string and the certificate to the data payload object. After signing, ``dataPayload.sign`` is changed from ``Null`` to ``String`` and ``dataPayload.cert`` is changed from ``Null`` to ``Object``. 295 | 296 | ------- 297 | Example 298 | ------- 299 | 300 | .. code-block:: javascript 301 | 302 | var owner = new Account(); 303 | owner.createCertificate({ 304 | expireDate: Date.now() + (365 * 24 * 60 * 60 * 1000), 305 | issuer: 'https://medibloc.org', 306 | issueDate: Date.now(), 307 | passphrase: '', 308 | }); 309 | var dataPayload = { 310 | hash: 'eb36d0606ff84bba5ae84e2af0f2197b2ff4272c3d22c46ffa27ca17851cea7f', 311 | }; 312 | owner.signDataPayload(dataPayload); 313 | console.log(dataPayload); 314 | > { 315 | hash: 'eb36d0606ff84bba5ae84e2af0f2197b2ff4272c3d22c46ffa27ca17851cea7f', 316 | sign: 'e04c9c20093d686224bd759e8ca272772ed0528251a80c43502a8e21d3dcbfea21827b37f199132fef58a0fd2325f0ed4aa4a94eaf17e67fe43ca491243bf1ec00', 317 | cert: { 318 | expireDate: 1558759447996, 319 | issuer: 'https://medibloc.org', 320 | issueDate: 1527223447996, 321 | pubKey: '02a980d3064c6135e75eb4843c5a15382d3dd4fa277625dea86f3fc97864eae288', 322 | signature: '9199402de763728112c68ddde02b06fbdb2745b0539ba5e981cb9a5233935c5e1e6f814fafe88f752e63635c77d48f58eea5024c552672d2aed761d14426e21d01' 323 | } 324 | } 325 | --------------------------------------------------------------------------------