├── .npmignore ├── CHANGELOG.md ├── .gitignore ├── examples ├── long.js ├── string_to_name.js ├── brainKey.js ├── name_type.js ├── signature.js ├── uint64.js ├── subscribe_data_transaction.js ├── chainStore.js ├── privKey.js ├── block.js ├── verify.js ├── pay_data_transaction.js ├── data_transaction_datasource_validate_error.js ├── update_data_transaction.js ├── create_data_transaction.js ├── sign.js ├── serializer.js ├── transfer.js └── dynamicSerializer.js ├── .editorconfig ├── .babelrc ├── .prettierrc ├── lib ├── chain │ ├── src │ │ ├── EmitterInstance.js │ │ ├── state.js │ │ ├── NumberUtils.js │ │ ├── ObjectId.js │ │ ├── AccountLogin.js │ │ ├── TransactionHelper.js │ │ ├── ChainTypes.js │ │ ├── ChainValidation.js │ │ └── TransactionBuilder.js │ └── index.js ├── ecc │ ├── src │ │ ├── BrainKey.js │ │ ├── enforce_types.js │ │ ├── hash.js │ │ ├── address.js │ │ ├── ecsignature.js │ │ ├── PublicKey.js │ │ ├── signature.js │ │ ├── ecdsa.js │ │ ├── PrivateKey.js │ │ ├── KeyUtils.js │ │ └── aes.js │ └── index.js ├── serializer │ ├── README.md │ ├── index.js │ └── src │ │ ├── template.js │ │ ├── error_with_cause.js │ │ ├── convert.js │ │ ├── FastParser.js │ │ ├── precision.js │ │ ├── serializer.js │ │ └── SerializerValidation.js ├── browser.js ├── index.js └── tx_serializer.js ├── esdoc.json ├── tools └── es2015Preset.js ├── .eslintrc ├── test ├── chain │ ├── Validation.js │ ├── NumberUtils.js │ ├── Login.js │ ├── ChainStore.js │ └── TransactionBuilder.js ├── serializer │ ├── operations_test.js │ ├── serialize.js │ ├── test_helper.js │ ├── static │ │ └── abi.js │ ├── all_types.js │ └── types_test.js └── ecc │ ├── KeyFormats.js │ └── Crypto.js ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── LICENSE ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | .idea 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v1.2.3 2 | - forked from bitsharesjs v1.2.3 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .npmrc 2 | npm-debug.log 3 | node_modules 4 | es 5 | dist 6 | .idea 7 | build 8 | -------------------------------------------------------------------------------- /examples/long.js: -------------------------------------------------------------------------------- 1 | import {Long} from 'bytebuffer'; 2 | 3 | let long = Long.fromString('18446744072631603071',true); 4 | console.log(long,long.toString()); 5 | 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | charset = utf-8 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /examples/string_to_name.js: -------------------------------------------------------------------------------- 1 | import { 2 | string_to_name, 3 | name_to_string 4 | } from "../lib/serializer/src/types"; 5 | 6 | let n = string_to_name("diceofferbet") 7 | console.log(n,name_to_string(n)); 8 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/env" 5 | ] 6 | ], 7 | "plugins": [ 8 | [ 9 | "@babel/plugin-transform-runtime" 10 | ] 11 | ] 12 | } -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": true, 4 | "singleQuote": true, 5 | "trailingComma": "none", 6 | "bracketSpacing": true, 7 | "jsxBracketSameLine": true, 8 | "arrowParens": "always" 9 | } 10 | -------------------------------------------------------------------------------- /lib/chain/src/EmitterInstance.js: -------------------------------------------------------------------------------- 1 | import ee from "event-emitter"; 2 | var _emitter; 3 | export default function emitter () { 4 | if ( !_emitter ) { 5 | _emitter = ee({}); 6 | } 7 | return _emitter; 8 | } 9 | -------------------------------------------------------------------------------- /examples/brainKey.js: -------------------------------------------------------------------------------- 1 | import { 2 | key 3 | } from "../lib"; 4 | 5 | console.log(key.get_brainPrivateKey("sticks bilious nonform piqure linaga jejune cellist dural golden cidaris subaud juniata nailing outjut balloon sasa").toWif()); -------------------------------------------------------------------------------- /esdoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": ".", 3 | "includes": ["\\.(js|es6)$"], 4 | "excludes": ["node_modules"], 5 | "destination": "./out/esdoc", 6 | "index": "./README.md", 7 | "package": "./package.json", 8 | "title": "GXBJS Docs" 9 | } 10 | -------------------------------------------------------------------------------- /lib/ecc/src/BrainKey.js: -------------------------------------------------------------------------------- 1 | export default function normalize(brainKey) { 2 | if (typeof brainKey !== 'string') { 3 | throw new Error('string required for brainKey'); 4 | } 5 | brainKey = brainKey.trim(); 6 | return brainKey.split(/[\t\n\v\f\r ]+/).join(' '); 7 | } 8 | -------------------------------------------------------------------------------- /examples/name_type.js: -------------------------------------------------------------------------------- 1 | import {name_to_string, object_id_type, string_to_name} from "../lib/serializer"; 2 | 3 | console.log(string_to_name('subbalance').toString()); 4 | console.log(name_to_string(string_to_name('subbalance'))); 5 | console.log(object_id_type("1.2.576").toString()); 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/serializer/README.md: -------------------------------------------------------------------------------- 1 | # About 2 | Binary serialization and deserialization library 3 | 4 | ```js 5 | import {} from "@graphene/serializer" 6 | ``` 7 | 8 | # Configure 9 | Update `./.npmrc` if you need to change something: 10 | ```bash 11 | @graphene/serializer:hex_dump = false 12 | 13 | ``` 14 | -------------------------------------------------------------------------------- /lib/chain/src/state.js: -------------------------------------------------------------------------------- 1 | function get(state) { 2 | return function(key) { 3 | return state[key] || ""; 4 | }; 5 | } 6 | 7 | function set(state) { 8 | return function(key, value) { 9 | state[key] = value; 10 | return this; 11 | }; 12 | } 13 | 14 | export { get, set }; 15 | -------------------------------------------------------------------------------- /tools/es2015Preset.js: -------------------------------------------------------------------------------- 1 | const buildPreset = require('babel-preset-es2015').buildPreset 2 | 3 | const BABEL_ENV = process.env.BABEL_ENV 4 | 5 | module.exports = { 6 | presets: [ 7 | [ buildPreset, { 8 | loose: true, 9 | modules: BABEL_ENV === 'es' ? false : 'commonjs' 10 | } ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "es6": true 7 | }, 8 | "extends": ["plugin:prettier/recommended", "eslint:recommended"], 9 | "parserOptions": { 10 | "parser": "babel-eslint", 11 | "sourceType": "module" 12 | }, 13 | "rules": {} 14 | } 15 | -------------------------------------------------------------------------------- /lib/browser.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | PrivateKey: require('./ecc/src/PrivateKey'), 3 | PublicKey: require('./ecc/src/PublicKey'), 4 | Signature: require('./ecc/src/signature'), 5 | key: require('./ecc/src/KeyUtils'), 6 | TransactionBuilder: require('./chain/src/TransactionBuilder'), 7 | Login: require('./chain/src/AccountLogin'), 8 | gxbjs_ws: require('gxbjs-ws') 9 | }; 10 | -------------------------------------------------------------------------------- /examples/signature.js: -------------------------------------------------------------------------------- 1 | import {Signature,PublicKey} from '../lib' 2 | 3 | let sig = '205bc8093489560c8c3b7ce7160290dd350c0da75284f898e7e8e0e55b01c4f9531a8dbcbe8bbc124785e50677045c12593b11da77372260f3bf0993b6ca49e627'; 4 | console.log(sig); 5 | console.log(Signature.fromHex(sig).verifyBuffer(Buffer.from("1111"),PublicKey.fromPublicKeyString('GXC6rAtkQUGJoxRR3gCEnYo2PxqtVNwD4zw9zg64qgrEpYqmjf2kh'))); 6 | -------------------------------------------------------------------------------- /lib/serializer/index.js: -------------------------------------------------------------------------------- 1 | import Serializer from "./src/serializer"; 2 | import fp from "./src/FastParser"; 3 | import types, {name_to_string, object_id_type, string_to_name} from "./src/types"; 4 | import * as ops from "./src/operations"; 5 | import template from "./src/template"; 6 | import SerializerValidation from "./src/SerializerValidation"; 7 | 8 | export {Serializer, fp, types, ops, template, SerializerValidation, string_to_name, name_to_string, object_id_type}; 9 | -------------------------------------------------------------------------------- /examples/uint64.js: -------------------------------------------------------------------------------- 1 | import Types from "../lib/serializer/src/types"; 2 | // var Types = require("../lib/serializer/src/types"); 3 | var ByteBuffer = require("bytebuffer"); 4 | 5 | let b = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN); 6 | const number = Types.uint64.fromObject("17446744123556677890"); 7 | Types.uint64.appendByteBuffer(b, number); 8 | console.log(Buffer.from(b.copy(0, b.offset).toBinary(), "binary").toString("hex")); 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/subscribe_data_transaction.js: -------------------------------------------------------------------------------- 1 | import {Apis} from "gxbjs-ws"; 2 | import {ChainStore,EmitterInstance} from "../lib"; 3 | let emitter = EmitterInstance(); 4 | 5 | Apis.instance("http://192.168.1.118:28090", true).init_promise.then((res) => { 6 | ChainStore.init().then(() => { 7 | ChainStore.subscribe_for_data_transaction(updateState); 8 | }); 9 | }); 10 | 11 | function updateState(object) { 12 | console.log(JSON.stringify(object,null,'\t')); 13 | } 14 | -------------------------------------------------------------------------------- /examples/chainStore.js: -------------------------------------------------------------------------------- 1 | import {Apis} from "gxbjs-ws"; 2 | import {ChainStore} from "../lib"; 3 | 4 | Apis.instance("wss://node5.gxb.io", true).init_promise.then((res) => { 5 | ChainStore.init().then(() => { 6 | ChainStore.subscribe(updateState); 7 | dynamicGlobal = ChainStore.getObject("2.1.0"); 8 | }); 9 | }); 10 | 11 | let dynamicGlobal = null; 12 | function updateState(object) { 13 | console.log("ChainStore object update\n", dynamicGlobal ? dynamicGlobal.toJS() : dynamicGlobal); 14 | } 15 | -------------------------------------------------------------------------------- /lib/serializer/src/template.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /** Console print any transaction object with zero default values. */ 3 | export default function template(op) { 4 | var object = op.toObject(void 0, { use_default: true, annotate: true }); 5 | 6 | // visual (with descriptions) 7 | console.error(JSON.stringify(object, null, 4)); 8 | 9 | // usable in a copy-paste 10 | 11 | object = op.toObject(void 0, { use_default: true, annotate: false }); 12 | 13 | // copy-paste one-lineer 14 | console.error(JSON.stringify(object)); 15 | } 16 | -------------------------------------------------------------------------------- /lib/ecc/index.js: -------------------------------------------------------------------------------- 1 | import Address from './src/address'; 2 | import Aes from './src/aes'; 3 | import PrivateKey from './src/PrivateKey'; 4 | import PublicKey from './src/PublicKey'; 5 | import Signature from './src/signature'; 6 | import ECSignature from './src/ecsignature'; 7 | import brainKey from './src/BrainKey'; 8 | import * as hash from './src/hash'; 9 | import key from './src/KeyUtils'; 10 | 11 | export { 12 | Address, 13 | Aes, 14 | PrivateKey, 15 | PublicKey, 16 | Signature, 17 | brainKey, 18 | hash, 19 | key, 20 | ECSignature 21 | }; 22 | -------------------------------------------------------------------------------- /test/chain/Validation.js: -------------------------------------------------------------------------------- 1 | import { ChainValidation } from "../../lib"; 2 | import assert from "assert"; 3 | 4 | describe("ChainValidation", () => { 5 | 6 | 7 | describe("is_object_id", () => { 8 | it("Is valid object id", ()=> { 9 | assert(ChainValidation.is_object_id("1.3.0") === true); 10 | }) 11 | 12 | it("Is not valid object id", ()=> { 13 | assert(ChainValidation.is_object_id("1.3") === false); 14 | }) 15 | 16 | it("Not string", ()=> { 17 | assert(ChainValidation.is_object_id(3) === false); 18 | }) 19 | }) 20 | 21 | 22 | }); 23 | -------------------------------------------------------------------------------- /examples/privKey.js: -------------------------------------------------------------------------------- 1 | 2 | import {PrivateKey, key} from "../lib"; 3 | 4 | let seed = "THIS IS A TERRIBLE BRAINKEY SEED WORD SEQUENCE"; 5 | let pkey = PrivateKey.fromSeed( key.normalize_brainKey(seed) ); 6 | 7 | console.log("\nPrivate key:", pkey.toWif()); 8 | console.log("Public key :", pkey.toPublicKey().toString(), "\n"); 9 | 10 | let start = new Date(); 11 | let result = pkey.get_shared_secret(pkey.toPublicKey().toPublicKeyString()); 12 | console.log('result:',result,'cost:',new Date()-start,'\n--------------'); 13 | start = new Date(); 14 | result = pkey.get_shared_secret_v2(pkey.toPublicKey().toPublicKeyString()); 15 | console.log('result:',result,'cost:',new Date()-start); -------------------------------------------------------------------------------- /lib/chain/index.js: -------------------------------------------------------------------------------- 1 | import ChainStore from "./src/ChainStore"; 2 | import TransactionBuilder from "./src/TransactionBuilder"; 3 | import ChainTypes from "./src/ChainTypes"; 4 | import ObjectId from "./src/ObjectId"; 5 | import NumberUtils from "./src/NumberUtils"; 6 | import TransactionHelper from "./src/TransactionHelper"; 7 | import ChainValidation from "./src/ChainValidation"; 8 | import EmitterInstance from "./src/EmitterInstance"; 9 | import Login from "./src/AccountLogin"; 10 | 11 | const {FetchChainObjects, FetchChain} = ChainStore; 12 | 13 | export {ChainStore, TransactionBuilder, FetchChainObjects, ChainTypes, 14 | ObjectId, NumberUtils, TransactionHelper, ChainValidation, FetchChain, Login } 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | 19 | **Your GXChain Account** 20 | You will be rewarded for you contributions 21 | -------------------------------------------------------------------------------- /test/serializer/operations_test.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import ops from '../../lib/serializer/src/operations'; 3 | 4 | describe("operation test", function() { 5 | 6 | it("templates", function() { 7 | for(let op in ops) { 8 | switch(op) { 9 | case "operation" : continue 10 | } 11 | template(ops[op]) 12 | } 13 | }) 14 | }) 15 | 16 | function template(op) { 17 | 18 | assert(op.toObject({}, {use_default: true})) 19 | assert(op.toObject({}, {use_default: true, annotate: true})) 20 | 21 | // sample json 22 | let obj = op.toObject({}, {use_default: true, annotate: false}) 23 | // console.log(" ", op.operation_name, "\t", JSON.stringify(obj), "\n") 24 | 25 | } 26 | -------------------------------------------------------------------------------- /examples/block.js: -------------------------------------------------------------------------------- 1 | import {Apis} from "gxbjs-ws"; 2 | import { 3 | ChainStore, 4 | hash, 5 | ops 6 | } from "../lib"; 7 | 8 | let block_id = '895414' 9 | Apis.instance("ws://192.168.2.244:28090", true).init_promise.then((res) => { 10 | ChainStore.init().then(() => { 11 | Apis.instance().db_api().exec("get_block", [block_id]).then(results => { 12 | results.transactions.forEach((transaction)=>{ 13 | let tr_buffer=ops.transaction.toBuffer(transaction) 14 | let result = hash.sha256(tr_buffer).toString('hex').substr(0,40); 15 | console.log(result); 16 | }); 17 | }).catch(error => { // in the event of an error clear the pending state for id 18 | console.error(error); 19 | }); 20 | }); 21 | }); 22 | 23 | -------------------------------------------------------------------------------- /test/chain/NumberUtils.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { NumberUtils } from "../../lib"; 3 | 4 | describe("Number utils", () => { 5 | 6 | it("to implied decimal", ()=> { 7 | assert.equal("1", NumberUtils.toImpliedDecimal(1, 0)) 8 | assert.equal("10", NumberUtils.toImpliedDecimal(1, 1)) 9 | assert.equal("100", NumberUtils.toImpliedDecimal(1, 2)) 10 | assert.equal("10", NumberUtils.toImpliedDecimal(".1", 2)) 11 | assert.equal("10", NumberUtils.toImpliedDecimal("0.1", 2)) 12 | assert.equal("10", NumberUtils.toImpliedDecimal("00.1", 2)) 13 | assert.equal("10", NumberUtils.toImpliedDecimal("00.10", 2)) 14 | assert.throws(()=> NumberUtils.toImpliedDecimal("00.100", 2)) 15 | assert.throws(()=> NumberUtils.toImpliedDecimal(9007199254740991 + 1, 1)) 16 | }) 17 | 18 | 19 | }) 20 | -------------------------------------------------------------------------------- /test/serializer/serialize.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { serializeCallData, deserializeCallData } from "../../lib/tx_serializer.js"; 3 | import { bank_abi, hello_abi } from "./static/abi.js"; 4 | 5 | describe("operation test", function () { 6 | it("serialize", function () { 7 | console.log("seeeeesss", serializeCallData("withdraw", { 8 | to_account: "youxiu123", 9 | amount: { amount: "100000", asset_id: "1" } 10 | }, bank_abi)); 11 | }); 12 | it("deserialize", function () { 13 | console.log("dessss", deserializeCallData("withdraw", "09796f75786975313233a0860100000000000100000000000000", bank_abi)); 14 | }); 15 | it("serialize", function () { 16 | console.log("seeeeesss", serializeCallData("hi", { 17 | user: "刘宗源" 18 | }, hello_abi)); 19 | }); 20 | }); -------------------------------------------------------------------------------- /examples/verify.js: -------------------------------------------------------------------------------- 1 | import { 2 | ChainStore, 3 | FetchChain, 4 | PrivateKey, 5 | TransactionHelper, 6 | Aes, 7 | TransactionBuilder, 8 | hash, 9 | Signature, 10 | ECSignature 11 | } from "../lib"; 12 | var privKey = ""; 13 | let pKey = PrivateKey.fromWif(privKey); 14 | var secp256k1 = require("secp256k1"); 15 | 16 | let str = JSON.stringify({"account_id":"1.2.19","data":"IvXmqAXcpIJDqkFoCZ6oWjbCU/d/EIbacEGrX2iUDL56jG3fpkoPFH17PvUWrbWW14MQl5XQwX+Lr9KM1ZkD/rmJVs4caSMmnaR7W5d4L68=","data_hash":"c637263ec17b0f21d924befa5dbddb67c7842155cf6acd696984b11cfb5b7001","request_id":"3e2aeb347de486f503f0afaf5b3cdf9cb9d6044b473f5225a19ee1feae37a083"} 17 | ); 18 | // let str ='1'; 19 | 20 | let start = new Date(); 21 | let sign1 = Signature.sign(str, pKey).toHex(); 22 | console.log('签名耗时:',new Date()-start,sign1); 23 | start = new Date(); 24 | let result = Signature.fromHex(sign1).verifyBuffer(str,pKey.toPublicKey()); 25 | console.log('验证结果:',result,'验证耗时:',new Date()-start); 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. iOS8.1] 31 | - Browser [e.g. stock browser, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | 37 | **Your GXChain Account** 38 | You will be rewarded for you contributions 39 | -------------------------------------------------------------------------------- /lib/ecc/src/enforce_types.js: -------------------------------------------------------------------------------- 1 | export default function enforce(type, value) { 2 | // Copied from https://github.com/bitcoinjs/bitcoinjs-lib 3 | switch (type) { 4 | case 'Array': { 5 | if (Array.isArray(value)) return; 6 | break; 7 | } 8 | 9 | case 'Boolean': { 10 | if (typeof value === 'boolean') return; 11 | break; 12 | } 13 | 14 | case 'Buffer': { 15 | if (Buffer.isBuffer(value)) return; 16 | break; 17 | } 18 | 19 | case 'Number': { 20 | if (typeof value === 'number') return; 21 | break; 22 | } 23 | 24 | case 'String': { 25 | if (typeof value === 'string') return; 26 | break; 27 | } 28 | 29 | default: { 30 | if (getName(value.constructor) === getName(type)) return; 31 | } 32 | } 33 | 34 | throw new TypeError('Expected ' + (getName(type) || type) + ', got ' + value); 35 | } 36 | 37 | function getName(fn) { 38 | // Why not fn.name: https://kangax.github.io/compat-table/es6/#function_name_property 39 | var match = fn.toString().match(/function (.*?)\(/); 40 | return match ? match[1] : null; 41 | } 42 | -------------------------------------------------------------------------------- /lib/serializer/src/error_with_cause.js: -------------------------------------------------------------------------------- 1 | /** Exception nesting. */ 2 | class ErrorWithCause { 3 | constructor(message, cause) { 4 | this.message = message; 5 | if ( 6 | typeof cause !== 'undefined' && cause !== null ? cause.message : undefined 7 | ) { 8 | this.message = `cause\t${cause.message}\t` + this.message; 9 | } 10 | 11 | var stack = ''; //(new Error).stack 12 | if ( 13 | typeof cause !== 'undefined' && cause !== null ? cause.stack : undefined 14 | ) { 15 | stack = `caused by\n\t${cause.stack}\t` + stack; 16 | } 17 | 18 | this.stack = this.message + '\n' + stack; 19 | } 20 | 21 | static throw(message, cause) { 22 | var msg = message; 23 | if ( 24 | typeof cause !== 'undefined' && cause !== null ? cause.message : undefined 25 | ) { 26 | msg += `\t cause: ${cause.message} `; 27 | } 28 | if ( 29 | typeof cause !== 'undefined' && cause !== null ? cause.stack : undefined 30 | ) { 31 | msg += `\n stack: ${cause.stack} `; 32 | } 33 | throw new Error(msg); 34 | } 35 | } 36 | 37 | export default ErrorWithCause; 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 GXChain 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/pay_data_transaction.js: -------------------------------------------------------------------------------- 1 | import {Apis} from "gxbjs-ws"; 2 | import {ChainStore, FetchChain, PrivateKey, TransactionHelper, Aes, TransactionBuilder, hash} from "../lib"; 3 | 4 | var privKey = ""; 5 | let pKey = PrivateKey.fromWif(privKey); 6 | 7 | Apis.instance("ws://192.168.1.118:28090", true) 8 | .init_promise.then((res) => { 9 | 10 | ChainStore.init().then(() => { 11 | 12 | let tr = new TransactionBuilder(); 13 | 14 | let request_id = "22e6debefca11b97bcb89554803c34d6c58bcbf8e7b50f2e290a46e3d14cabfb"; 15 | 16 | tr.add_type_operation('pay_data_transaction', { 17 | fee: { 18 | amount: 0, 19 | asset_id: "1.3.0" 20 | }, 21 | request_id: request_id 22 | }); 23 | 24 | tr.set_required_fees().then(() => { 25 | tr.add_signer(pKey); 26 | console.log("serialized transaction:", JSON.stringify(tr.serialize(), null, '\t')); 27 | tr.broadcast(); 28 | }, (ex)=> { 29 | console.error(ex); 30 | }) 31 | }, (ex)=> { 32 | console.error(ex); 33 | }); 34 | }, (ex)=> { 35 | console.error(ex); 36 | }); 37 | -------------------------------------------------------------------------------- /lib/serializer/src/convert.js: -------------------------------------------------------------------------------- 1 | import ByteBuffer from 'bytebuffer'; 2 | 3 | export default function(type) { 4 | return { 5 | fromHex(hex) { 6 | var b = ByteBuffer.fromHex(hex, ByteBuffer.LITTLE_ENDIAN); 7 | return type.fromByteBuffer(b); 8 | }, 9 | 10 | toHex(object) { 11 | var b = toByteBuffer(type, object); 12 | return b.toHex(); 13 | }, 14 | 15 | fromBuffer(buffer) { 16 | var b = ByteBuffer.fromBinary( 17 | buffer.toString(), 18 | ByteBuffer.LITTLE_ENDIAN 19 | ); 20 | return type.fromByteBuffer(b); 21 | }, 22 | 23 | toBuffer(object) { 24 | return new Buffer(toByteBuffer(type, object).toBinary(), 'binary'); 25 | }, 26 | 27 | fromBinary(string) { 28 | var b = ByteBuffer.fromBinary(string, ByteBuffer.LITTLE_ENDIAN); 29 | return type.fromByteBuffer(b); 30 | }, 31 | 32 | toBinary(object) { 33 | return toByteBuffer(type, object).toBinary(); 34 | } 35 | }; 36 | } 37 | 38 | var toByteBuffer = function(type, object) { 39 | var b = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN); 40 | type.appendByteBuffer(b, object); 41 | return b.copy(0, b.offset); 42 | }; 43 | -------------------------------------------------------------------------------- /examples/data_transaction_datasource_validate_error.js: -------------------------------------------------------------------------------- 1 | import {Apis} from "gxbjs-ws"; 2 | import {ChainStore, FetchChain, PrivateKey, TransactionHelper, Aes, TransactionBuilder, hash} from "../lib"; 3 | 4 | var privKey = ""; 5 | let pKey = PrivateKey.fromWif(privKey); 6 | 7 | Apis.instance("ws://192.168.1.118:28090", true) 8 | .init_promise.then((res) => { 9 | 10 | ChainStore.init().then(() => { 11 | 12 | let tr = new TransactionBuilder(); 13 | 14 | let request_id = "0793bbb9133b8f4f202619a0f6f92e5d9e44010ece00ec57830c8993014fdc2c"; 15 | 16 | tr.add_type_operation('data_transaction_datasource_validate_error', { 17 | fee: { 18 | amount: 0, 19 | asset_id: "1.3.0" 20 | }, 21 | request_id: request_id, 22 | datasource:'1.2.19' 23 | }); 24 | 25 | tr.set_required_fees().then(() => { 26 | tr.add_signer(pKey); 27 | console.log("serialized transaction:", JSON.stringify(tr.serialize(), null, '\t')); 28 | tr.broadcast(); 29 | }, (ex)=> { 30 | console.error(ex); 31 | }) 32 | }, (ex)=> { 33 | console.error(ex); 34 | }); 35 | }, (ex)=> { 36 | console.error(ex); 37 | }); 38 | -------------------------------------------------------------------------------- /examples/update_data_transaction.js: -------------------------------------------------------------------------------- 1 | import {Apis} from "gxbjs-ws"; 2 | import {ChainStore, FetchChain, PrivateKey, TransactionHelper, Aes, TransactionBuilder, hash} from "../lib"; 3 | 4 | var privKey = ""; 5 | let pKey = PrivateKey.fromWif(privKey); 6 | 7 | Apis.instance("ws://192.168.1.118:28090", true) 8 | .init_promise.then((res) => { 9 | 10 | ChainStore.init().then(() => { 11 | 12 | let tr = new TransactionBuilder(); 13 | 14 | let request_id = "0793bbb9133b8f4f202619a0f6f92e5d9e44010ece00ec57830c8993014fdc2c"; 15 | 16 | tr.add_type_operation('data_transaction_update', { 17 | request_id: request_id, 18 | new_status: 1, 19 | fee: { 20 | amount: 0, 21 | asset_id: "1.3.0" 22 | }, 23 | new_requester: "1.2.19", 24 | memo:"" 25 | }); 26 | 27 | tr.set_required_fees().then(() => { 28 | tr.add_signer(pKey); 29 | console.log("serialized transaction:", JSON.stringify(tr.serialize(), null, '\t')); 30 | tr.broadcast(); 31 | }, (ex)=> { 32 | console.error(ex); 33 | }) 34 | }, (ex)=> { 35 | console.error(ex); 36 | }); 37 | }, (ex)=> { 38 | console.error(ex); 39 | }); 40 | -------------------------------------------------------------------------------- /lib/chain/src/NumberUtils.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | 3 | /** 4 | Convert 12.34 with a precision of 3 into 12340 5 | 6 | @arg {number|string} number - Use strings for large numbers. This may contain one decimal but no sign 7 | @arg {number} precision - number of implied decimal places (usually causes right zero padding) 8 | @return {string} - 9 | */ 10 | 11 | const NumberUtils = { 12 | toImpliedDecimal: function toImpliedDecimal(number, precision) { 13 | if(typeof number === "number") { 14 | assert(number <= 9007199254740991, "overflow") 15 | number = ""+number; 16 | } else 17 | if( number.toString ) 18 | number = number.toString() 19 | 20 | assert(typeof number === "string", "number should be an actual number or string: " + (typeof number)) 21 | number = number.trim() 22 | assert(/^[0-9]*\.?[0-9]*$/.test(number), "Invalid decimal number " + number) 23 | 24 | let [ whole = "", decimal = ""] = number.split(".") 25 | 26 | let padding = precision - decimal.length 27 | assert(padding >= 0, "Too many decimal digits in " + number + " to create an implied decimal of " + precision) 28 | 29 | for(let i = 0; i < padding; i++) 30 | decimal += "0" 31 | 32 | while(whole.charAt(0) === "0") 33 | whole = whole.substring(1) 34 | 35 | return whole + decimal 36 | } 37 | } 38 | 39 | export default NumberUtils; 40 | -------------------------------------------------------------------------------- /lib/serializer/src/FastParser.js: -------------------------------------------------------------------------------- 1 | import PublicKey from '../../ecc/src/PublicKey'; 2 | 3 | class FastParser { 4 | static fixed_data(b, len, buffer) { 5 | if (!b) { 6 | return; 7 | } 8 | if (buffer) { 9 | let data = buffer.slice(0, len).toString('binary'); 10 | b.append(data, 'binary'); 11 | while (len-- > data.length) { 12 | b.writeUint8(0); 13 | } 14 | } else { 15 | let b_copy = b.copy(b.offset, b.offset + len); 16 | b.skip(len); 17 | return new Buffer(b_copy.toBinary(), 'binary'); 18 | } 19 | } 20 | 21 | static public_key(b, public_key) { 22 | if (!b) { 23 | return; 24 | } 25 | if (public_key) { 26 | var buffer = public_key.toBuffer(); 27 | b.append(buffer.toString('binary'), 'binary'); 28 | return; 29 | } else { 30 | buffer = FastParser.fixed_data(b, 33); 31 | return PublicKey.fromBuffer(buffer); 32 | } 33 | } 34 | 35 | static ripemd160(b, ripemd160) { 36 | if (!b) { 37 | return; 38 | } 39 | if (ripemd160) { 40 | FastParser.fixed_data(b, 20, ripemd160); 41 | return; 42 | } else { 43 | return FastParser.fixed_data(b, 20); 44 | } 45 | } 46 | 47 | static time_point_sec(b, epoch) { 48 | if (epoch) { 49 | epoch = Math.ceil(epoch / 1000); 50 | b.writeInt32(epoch); 51 | return; 52 | } else { 53 | epoch = b.readInt32(); // fc::time_point_sec 54 | return new Date(epoch * 1000); 55 | } 56 | } 57 | } 58 | 59 | export default FastParser; 60 | -------------------------------------------------------------------------------- /lib/ecc/src/hash.js: -------------------------------------------------------------------------------- 1 | import createHash from 'create-hash'; 2 | import createHmac from 'create-hmac'; 3 | 4 | /** @arg {string|Buffer} data 5 | @arg {string} [digest = null] - 'hex', 'binary' or 'base64' 6 | @return {string|Buffer} - Buffer when digest is null, or string 7 | */ 8 | function sha1(data, encoding) { 9 | return createHash('sha1') 10 | .update(data) 11 | .digest(encoding); 12 | } 13 | 14 | /** @arg {string|Buffer} data 15 | @arg {string} [digest = null] - 'hex', 'binary' or 'base64' 16 | @return {string|Buffer} - Buffer when digest is null, or string 17 | */ 18 | function sha256(data, encoding) { 19 | return createHash('sha256') 20 | .update(data) 21 | .digest(encoding); 22 | } 23 | 24 | /** @arg {string|Buffer} data 25 | @arg {string} [digest = null] - 'hex', 'binary' or 'base64' 26 | @return {string|Buffer} - Buffer when digest is null, or string 27 | */ 28 | function sha512(data, encoding) { 29 | return createHash('sha512') 30 | .update(data) 31 | .digest(encoding); 32 | } 33 | 34 | function HmacSHA256(buffer, secret) { 35 | return createHmac('sha256', secret) 36 | .update(buffer) 37 | .digest(); 38 | } 39 | 40 | function ripemd160(data) { 41 | return createHash('rmd160') 42 | .update(data) 43 | .digest(); 44 | } 45 | 46 | // function hash160(buffer) { 47 | // return ripemd160(sha256(buffer)) 48 | // } 49 | // 50 | // function hash256(buffer) { 51 | // return sha256(sha256(buffer)) 52 | // } 53 | 54 | // 55 | // function HmacSHA512(buffer, secret) { 56 | // return crypto.createHmac('sha512', secret).update(buffer).digest() 57 | // } 58 | 59 | export { sha1, sha256, sha512, HmacSHA256, ripemd160 }; 60 | -------------------------------------------------------------------------------- /test/serializer/test_helper.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | 3 | module.exports = { 4 | 5 | print_result(tr_object){ 6 | if (tr_object) { 7 | console.log('print_result', JSON.stringify(tr_object)); 8 | } 9 | try { 10 | var tr = signed_transaction_type.fromObject(tr_object); 11 | var tr_hex = signed_transaction_type.toHex(tr); 12 | return ByteBuffer.fromHex(tr_hex).printDebug(); 13 | } catch (e) { 14 | if (tr_object && tr_object["ref_block_num"]) { 15 | return console.log("print_result: unparsed or non-transactoin object",e,e.stack); 16 | } 17 | } 18 | }, 19 | 20 | print_hex(hex){ 21 | console.log('print_hex'); 22 | ByteBuffer.fromHex(hex).printDebug(); 23 | try { 24 | var tr = signed_transaction_type.fromHex(hex); 25 | var tr_object = signed_transaction_type.toObject(tr); 26 | return console.log(JSON.stringify(tr_object)); 27 | } catch (e) { 28 | return console.log("print_hex: unparsed or non-transactoin object",e,e.stack); 29 | } 30 | }, 31 | 32 | log_error(error){ 33 | if (error.stack) { 34 | return console.log('ERROR',error.stack); 35 | } else { 36 | return console.log('ERROR',error); 37 | } 38 | }, 39 | 40 | error(message_substring, f){ 41 | var fail = false; 42 | try { 43 | f(); 44 | fail = true; 45 | } catch (e) { 46 | if (e.toString().indexOf(message_substring) === -1) { 47 | throw new Error("expecting " + message_substring); 48 | } 49 | } 50 | if (fail) { 51 | throw new Error("expecting " + message_substring); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /examples/create_data_transaction.js: -------------------------------------------------------------------------------- 1 | import {Apis} from "gxbjs-ws"; 2 | import {ChainStore, FetchChain, PrivateKey, TransactionHelper, Aes, TransactionBuilder,hash} from "../lib"; 3 | 4 | var privKey = ""; 5 | let pKey = PrivateKey.fromWif(privKey); 6 | 7 | Apis.instance("ws://192.168.1.118:28090", true) 8 | .init_promise.then((res) => { 9 | 10 | let pubKey =pKey.toPublicKey().toPublicKeyString(); 11 | 12 | ChainStore.init().then(() => { 13 | 14 | let tr = new TransactionBuilder(); 15 | let nonce = TransactionHelper.unique_nonce_uint64(); 16 | let request_id = hash.sha256(pubKey + nonce).toString('hex'); 17 | 18 | // let request_id = "04856f2c062af25803faafc4a061e2a3961dda45a94ab3af0e67a4f7794b399a33"; 19 | 20 | tr.add_type_operation('data_transaction_create', { 21 | request_id: request_id, 22 | product_id: "1.17.0", 23 | version: "1.0.0", 24 | params: "params", 25 | fee: { 26 | amount: 0, 27 | asset_id: "1.3.0" 28 | }, 29 | requester: "1.2.19", 30 | create_date_time: new Date().toISOString().split('.')[0] 31 | }); 32 | 33 | tr.set_required_fees().then(() => { 34 | tr.add_signer(pKey); 35 | console.log("serialized transaction:", JSON.stringify(tr.serialize(),null,'\t')); 36 | ChainStore.subscribe(function (obj) { 37 | console.log(obj); 38 | }) 39 | tr.broadcast(function (result) { 40 | console.log('result:',result); 41 | }).catch((ex)=>{ 42 | let balance = /Insufficient Balance/.test(ex.message); 43 | }); 44 | }, (ex)=> { 45 | console.error(ex); 46 | }) 47 | 48 | },(ex)=> { 49 | console.error(ex); 50 | }); 51 | },(ex)=> { 52 | console.error(ex); 53 | }); 54 | -------------------------------------------------------------------------------- /examples/sign.js: -------------------------------------------------------------------------------- 1 | import { 2 | ChainStore, 3 | FetchChain, 4 | PrivateKey, 5 | TransactionHelper, 6 | Aes, 7 | TransactionBuilder, 8 | hash, 9 | Signature, 10 | ECSignature 11 | } from "../lib"; 12 | var privKey = ""; 13 | let pKey = PrivateKey.fromWif(privKey); 14 | var secp256k1 = require("secp256k1"); 15 | 16 | let str = "asasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdas123dasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasdasasdasdasd"; 17 | // let str ='1'; 18 | 19 | let start = new Date(); 20 | let sign1 = Signature.sign(str, pKey).toHex(); 21 | console.log('老方法签名结果:', sign1); 22 | console.log('老方法签名耗时:', new Date().getTime() - start); 23 | 24 | start = new Date(); 25 | let hashStr = hash.sha256(new Buffer(str)); 26 | let nonce = 0; 27 | let sigDER = null; 28 | let sig = null; 29 | while (true) { 30 | sig = secp256k1.sign(hashStr, pKey.toBuffer(), { 31 | noncefn: function () { 32 | return hash.sha256(new Buffer(hashStr + (nonce++))); 33 | } 34 | }); 35 | sigDER = secp256k1.signatureExport(sig.signature); 36 | let lenR = sigDER[3]; 37 | let lenS = sigDER[5 + lenR]; 38 | console.log(sigDER, lenR, lenS); 39 | if (lenR === 32 && lenS === 32) { 40 | console.log(sigDER, nonce); 41 | break; 42 | } 43 | if (nonce % 10 === 0) { 44 | console.log("WARN: " + nonce + " attempts to find canonical signature"); 45 | } 46 | } 47 | 48 | let ecsig = ECSignature.fromDER(sigDER); 49 | let signature = new Signature(ecsig.r, ecsig.s, sig.recovery + 31); 50 | console.log('新方法签名结果:', signature.toHex()); 51 | console.log('新方法签名耗时:', new Date().getTime() - start); 52 | 53 | -------------------------------------------------------------------------------- /lib/chain/src/ObjectId.js: -------------------------------------------------------------------------------- 1 | import {Long} from 'bytebuffer'; 2 | 3 | import v from '../../serializer/src/SerializerValidation'; 4 | 5 | var DB_MAX_INSTANCE_ID = Long.fromNumber(((Math.pow(2,48))-1)); 6 | 7 | class ObjectId { 8 | 9 | constructor(space,type,instance){ 10 | this.space = space; 11 | this.type = type; 12 | this.instance = instance; 13 | var instance_string = this.instance.toString(); 14 | var ObjectId = `${this.space}.${this.type}.${instance_string}`; 15 | if (!v.is_digits(instance_string)) { 16 | throw new `Invalid object id ${ObjectId}`(); 17 | } 18 | } 19 | 20 | static fromString(value){ 21 | if ( 22 | value.space !== undefined && 23 | value.type !== undefined && 24 | value.instance !== undefined 25 | ) { 26 | return value; 27 | } 28 | 29 | var params = v.require_match( 30 | /^([0-9]+)\.([0-9]+)\.([0-9]+)$/, 31 | v.required(value, "ObjectId"), 32 | "ObjectId" 33 | ); 34 | return new ObjectId( 35 | parseInt(params[1]), 36 | parseInt(params[2]), 37 | Long.fromString(params[3]) 38 | ); 39 | } 40 | 41 | static fromLong(long){ 42 | var space = long.shiftRight(56).toInt(); 43 | var type = long.shiftRight(48).toInt() & 0x00ff; 44 | var instance = long.and(DB_MAX_INSTANCE_ID); 45 | return new ObjectId(space, type, instance); 46 | } 47 | 48 | static fromByteBuffer(b){ 49 | return ObjectId.fromLong(b.readUint64()); 50 | } 51 | 52 | toLong() { 53 | return Long.fromNumber(this.space).shiftLeft(56).or( 54 | Long.fromNumber(this.type).shiftLeft(48).or(this.instance) 55 | ); 56 | } 57 | 58 | appendByteBuffer(b){ 59 | return b.writeUint64(this.toLong()); 60 | } 61 | 62 | toString() { 63 | return `${this.space}.${this.type}.${this.instance.toString()}`; 64 | } 65 | } 66 | 67 | export default ObjectId; 68 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /* Serializer */ 2 | import Serializer from './serializer/src/serializer'; 3 | import fp from './serializer/src/FastParser'; 4 | import types, { 5 | name_to_string, 6 | object_id_type, 7 | string_to_name 8 | } from './serializer/src/types'; 9 | import * as ops from './serializer/src/operations'; 10 | import template from './serializer/src/template'; 11 | import SerializerValidation from './serializer/src/SerializerValidation'; 12 | /* ECC */ 13 | import Address from './ecc/src/address'; 14 | import Aes from './ecc/src/aes'; 15 | import PrivateKey from './ecc/src/PrivateKey'; 16 | import PublicKey from './ecc/src/PublicKey'; 17 | import Signature from './ecc/src/signature'; 18 | import ECSignature from './ecc/src/ecsignature'; 19 | import brainKey from './ecc/src/BrainKey'; 20 | import * as hash from './ecc/src/hash'; 21 | import key from './ecc/src/KeyUtils'; 22 | /* Chain */ 23 | import ChainStore from './chain/src/ChainStore'; 24 | import TransactionBuilder from './chain/src/TransactionBuilder'; 25 | import ChainTypes from './chain/src/ChainTypes'; 26 | import ObjectId from './chain/src/ObjectId'; 27 | import NumberUtils from './chain/src/NumberUtils'; 28 | import TransactionHelper from './chain/src/TransactionHelper'; 29 | import ChainValidation from './chain/src/ChainValidation'; 30 | import EmitterInstance from './chain/src/EmitterInstance'; 31 | import Login from './chain/src/AccountLogin'; 32 | 33 | export { 34 | Serializer, 35 | fp, 36 | types, 37 | ops, 38 | template, 39 | SerializerValidation, 40 | name_to_string, 41 | string_to_name, 42 | object_id_type 43 | }; 44 | 45 | export { 46 | Address, 47 | Aes, 48 | PrivateKey, 49 | PublicKey, 50 | Signature, 51 | brainKey, 52 | hash, 53 | key, 54 | ECSignature 55 | }; 56 | 57 | // in order to use webpack tree shaking to reduce bundle size 58 | // const {FetchChainObjects, FetchChain} = ChainStore; 59 | 60 | export { 61 | ChainStore, 62 | TransactionBuilder, 63 | ChainTypes, 64 | EmitterInstance, 65 | ObjectId, 66 | NumberUtils, 67 | TransactionHelper, 68 | ChainValidation, 69 | Login 70 | }; 71 | -------------------------------------------------------------------------------- /lib/tx_serializer.js: -------------------------------------------------------------------------------- 1 | import types from './serializer/src/types'; 2 | import * as ops from './serializer/src/operations'; 3 | 4 | import { Serializer } from './serializer'; 5 | 6 | function isArrayType(type) { 7 | return type.indexOf('[]') !== -1; 8 | } 9 | 10 | /** 11 | * serialize call data 12 | * @param {String} action - call action 13 | * @param {Object} params - call params 14 | * @param {Object} abi 15 | */ 16 | export const serializeCallData = (action, params, abi) => { 17 | const ser = makeSerializer(abi, action); 18 | return ser.toBuffer(params); 19 | }; 20 | 21 | /** 22 | * deserialize call data 23 | * @param {String} action - call action 24 | * @param {String} data - data hex string 25 | * @param {Object} abi - contract abi 26 | */ 27 | export function deserializeCallData(action, data, abi) { 28 | // construct serializer 29 | const serializer = makeSerializer(abi, action); 30 | // dserialize from hex 31 | return serializer.toObject(serializer.fromHex(data)); 32 | } 33 | 34 | export const serializeTransaction = (transaction) => { 35 | return ops.transaction.toBuffer(ops.transaction.fromObject(transaction)); 36 | }; 37 | 38 | /** 39 | * make serializer by abi and action 40 | * @param {Object} abi 41 | * @param {String} action 42 | */ 43 | export function makeSerializer(abi, action) { 44 | abi = JSON.parse(JSON.stringify(abi)); 45 | // let struct = Array.find(abi.structs, (s) => s.name === action); 46 | let struct = abi.structs.find((s) => s.name === action); 47 | const typeObj = {}; 48 | 49 | struct.fields.forEach((f) => { 50 | let isArrayFlag = false; 51 | if (isArrayType(f.type)) { 52 | isArrayFlag = true; 53 | f.type = f.type.split('[')[0]; 54 | } 55 | 56 | let type = types[f.type]; 57 | if (!type) { 58 | // let t = Array.find(abi.types, (t) => t.new_type_name === f.type); 59 | let t = abi.types.find((t) => t.new_type_name === f.type); 60 | if (t) { 61 | type = types[t.type]; 62 | } 63 | } 64 | if (!type) { 65 | type = ops[f.type]; 66 | } 67 | 68 | if (type) { 69 | if (isArrayFlag) { 70 | type = types.array(type, true); 71 | } 72 | } 73 | 74 | typeObj[f.name] = type; 75 | }); 76 | 77 | return new Serializer('temp', typeObj); 78 | } 79 | -------------------------------------------------------------------------------- /lib/ecc/src/address.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; 2 | import { ChainConfig } from 'gxbjs-ws'; 3 | import { sha256, sha512, ripemd160 } from './hash'; 4 | import { encode, decode } from 'bs58'; 5 | import deepEqual from 'deep-equal'; 6 | 7 | /** Addresses are shortened non-reversable hashes of a public key. The full PublicKey is preferred. 8 | @deprecated 9 | */ 10 | class Address { 11 | constructor(addy) { 12 | this.addy = addy; 13 | } 14 | 15 | static fromBuffer(buffer) { 16 | var _hash = sha512(buffer); 17 | var addy = ripemd160(_hash); 18 | return new Address(addy); 19 | } 20 | 21 | static fromString(string, address_prefix = ChainConfig.address_prefix) { 22 | var prefix = string.slice(0, address_prefix.length); 23 | assert.equal( 24 | address_prefix, 25 | prefix, 26 | `Expecting key to begin with ${address_prefix}, instead got ${prefix}` 27 | ); 28 | var addy = string.slice(address_prefix.length); 29 | addy = new Buffer(decode(addy), 'binary'); 30 | var checksum = addy.slice(-4); 31 | addy = addy.slice(0, -4); 32 | var new_checksum = ripemd160(addy); 33 | new_checksum = new_checksum.slice(0, 4); 34 | var isEqual = deepEqual(checksum, new_checksum); //, 'Invalid checksum' 35 | if (!isEqual) { 36 | throw new Error('Checksum did not match'); 37 | } 38 | return new Address(addy); 39 | } 40 | 41 | /** @return Address - Compressed PTS format (by default) */ 42 | static fromPublic(public_key, compressed = true, version = 56) { 43 | var sha2 = sha256(public_key.toBuffer(compressed)); 44 | var rep = ripemd160(sha2); 45 | var versionBuffer = new Buffer(1); 46 | versionBuffer.writeUInt8(0xff & version, 0); 47 | var addr = Buffer.concat([versionBuffer, rep]); 48 | var check = sha256(addr); 49 | check = sha256(check); 50 | var buffer = Buffer.concat([addr, check.slice(0, 4)]); 51 | return new Address(ripemd160(buffer)); 52 | } 53 | 54 | toBuffer() { 55 | return this.addy; 56 | } 57 | 58 | toString(address_prefix = ChainConfig.address_prefix) { 59 | var checksum = ripemd160(this.addy); 60 | var addy = Buffer.concat([this.addy, checksum.slice(0, 4)]); 61 | return address_prefix + encode(addy); 62 | } 63 | } 64 | 65 | export default Address; 66 | -------------------------------------------------------------------------------- /test/serializer/static/abi.js: -------------------------------------------------------------------------------- 1 | export const bank_abi = { 2 | "version": "gxc::abi/1.0", 3 | "types": [], 4 | "structs": [ 5 | { 6 | "name": "account", 7 | "base": "", 8 | "fields": [ 9 | { 10 | "name": "owner", 11 | "type": "uint64" 12 | }, 13 | { 14 | "name": "balances", 15 | "type": "contract_asset[]" 16 | } 17 | ] 18 | }, 19 | { 20 | "name": "deposit", 21 | "base": "", 22 | "fields": [] 23 | }, 24 | { 25 | "name": "withdraw", 26 | "base": "", 27 | "fields": [ 28 | { 29 | "name": "to_account", 30 | "type": "string" 31 | }, 32 | { 33 | "name": "amount", 34 | "type": "contract_asset" 35 | } 36 | ] 37 | } 38 | ], 39 | "actions": [ 40 | { 41 | "name": "deposit", 42 | "type": "deposit", 43 | "payable": true 44 | }, 45 | { 46 | "name": "withdraw", 47 | "type": "withdraw", 48 | "payable": false 49 | } 50 | ], 51 | "tables": [ 52 | { 53 | "name": "account", 54 | "index_type": "i64", 55 | "key_names": [ 56 | "owner" 57 | ], 58 | "key_types": [ 59 | "uint64" 60 | ], 61 | "type": "account" 62 | } 63 | ], 64 | "error_messages": [], 65 | "abi_extensions": [] 66 | }; 67 | 68 | export const hello_abi = { 69 | "____comment": "This file was generated by gxc-abigen. DO NOT EDIT - 2019-03-11T06:23:05", 70 | "version": "gxc::abi/1.0", 71 | "types": [], 72 | "structs": [{ 73 | "name": "hi", 74 | "base": "", 75 | "fields": [{ 76 | "name": "user", 77 | "type": "string" 78 | } 79 | ] 80 | } 81 | ], 82 | "actions": [{ 83 | "name": "hi", 84 | "type": "hi", 85 | "payable": false 86 | } 87 | ], 88 | "tables": [], 89 | "error_messages": [], 90 | "abi_extensions": [] 91 | }; -------------------------------------------------------------------------------- /test/chain/Login.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {Login as login} from "../../lib"; 3 | import {Login as login2} from "../../lib"; 4 | 5 | var auths = { 6 | active: [ 7 | ["GPH5Abm5dCdy3hJ1C5ckXkqUH2Me7dXqi9Y7yjn9ACaiSJ9h8r8mL", 1] 8 | ] 9 | } 10 | 11 | describe("AccountLogin", () => { 12 | 13 | afterEach(function() { 14 | login.setRoles(["active", "owner", "memo"]); 15 | }); 16 | 17 | describe("Instance", function() { 18 | it ("Instantiates with default roles", function() { 19 | let roles = login.get("roles"); 20 | 21 | assert(roles.length ); 22 | assert(roles[0] === "active"); 23 | assert(roles[1] === "owner"); 24 | assert(roles[2] === "memo"); 25 | }); 26 | 27 | it ("Is singleton", function() { 28 | login.setRoles(["singleton"]); 29 | 30 | let roles = login2.get("roles"); 31 | assert(roles.length === 1 ); 32 | assert(roles[0] === "singleton"); 33 | }); 34 | }); 35 | 36 | describe("Methods", function() { 37 | 38 | it ("Set roles", function() { 39 | login.setRoles(["active"]); 40 | let roles = login.get("roles"); 41 | 42 | assert(roles.length === 1 ); 43 | assert(roles[0] === "active" ); 44 | }); 45 | 46 | it ("Requires 12 char password", function() { 47 | assert.throws(login.generateKeys, Error); 48 | }); 49 | 50 | it ("Generate keys with no role input", function() { 51 | let {privKeys, pubKeys} = login.generateKeys("someaccountname", "somereallylongpassword"); 52 | 53 | assert(Object.keys(privKeys).length === 3); 54 | assert(Object.keys(pubKeys).length === 3); 55 | }); 56 | 57 | it ("Generate keys with role input", function() { 58 | let {privKeys, pubKeys} = login.generateKeys("someaccountname", "somereallylongpassword", ["active"]); 59 | 60 | assert(privKeys.active); 61 | assert(Object.keys(privKeys).length === 1); 62 | assert(Object.keys(pubKeys).length === 1); 63 | }); 64 | 65 | it ("Check keys", function() { 66 | let success = login.checkKeys({ 67 | accountName: "someaccountname", 68 | password: "somereallylongpassword", 69 | auths: auths 70 | }); 71 | 72 | assert(true, success); 73 | 74 | }); 75 | }) 76 | 77 | }); 78 | -------------------------------------------------------------------------------- /test/chain/ChainStore.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {Apis, ChainConfig} from "gxbjs-ws"; 3 | import { FetchChain, ChainStore } from "../../lib"; 4 | 5 | var coreAsset; 6 | 7 | describe("ChainStore", () => { 8 | // Connect once for all tests 9 | before(function() { 10 | /* use wss://bitshares.openledger.info/ws if no local node is available */ 11 | return Apis.instance("ws://127.0.0.1:8090", true).init_promise.then(function (result) { 12 | coreAsset = result[0].network.core_asset; 13 | ChainStore.init(); 14 | }); 15 | }); 16 | 17 | // Unsubscribe everything after each test 18 | afterEach(function() { 19 | ChainStore.subscribers = new Set(); 20 | ChainStore.clearCache(); 21 | }); 22 | 23 | after(function() { 24 | ChainConfig.reset(); 25 | }); 26 | 27 | describe("Subscriptions", function() { 28 | 29 | it("Asset not found", function() { 30 | return new Promise( function(resolve) { 31 | ChainStore.subscribe(function() { 32 | assert(ChainStore.getAsset("NOTFOUND") === null) 33 | resolve() 34 | }) 35 | assert(ChainStore.getAsset("NOTFOUND") === undefined) 36 | }) 37 | }) 38 | 39 | it("Asset by name", function() { 40 | return new Promise( function(resolve) { 41 | ChainStore.subscribe(function() { 42 | assert(ChainStore.getAsset(coreAsset) != null) 43 | resolve() 44 | }) 45 | assert(ChainStore.getAsset(coreAsset) === undefined) 46 | }) 47 | }) 48 | 49 | it("Asset by id", function() { 50 | return new Promise( function(resolve) { 51 | ChainStore.subscribe(function() { 52 | assert(ChainStore.getAsset("1.3.0") != null) 53 | resolve() 54 | }) 55 | assert(ChainStore.getAsset("1.3.0") === undefined) 56 | }) 57 | }) 58 | 59 | it("Object by id", function() { 60 | return new Promise( function(resolve) { 61 | ChainStore.subscribe(function() { 62 | assert(ChainStore.getAsset("2.0.0") != null) 63 | resolve() 64 | }) 65 | assert(ChainStore.getAsset("2.0.0") === undefined) 66 | }) 67 | }) 68 | 69 | 70 | }) 71 | // ChainStore.getAccount("not found") 72 | // 73 | // ChainStore.unsubscribe(cb) 74 | // // return FetchChain("getAccount", "notfound") 75 | // let cb = res => console.log('res',res) 76 | // // }) 77 | // }) 78 | 79 | 80 | }) 81 | -------------------------------------------------------------------------------- /examples/serializer.js: -------------------------------------------------------------------------------- 1 | import {serializeTransaction} from "../lib/tx_serializer"; 2 | import { Signature, PrivateKey } from "../lib/ecc"; 3 | 4 | console.log(serializeTransaction({ 5 | "expiration" : "2019-01-28T14:34:06", 6 | "extensions" : [ 7 | 8 | ], 9 | "operations" : [ 10 | [ 11 | 6, 12 | { 13 | "account" : "1.2.1110", 14 | "new_options" : { 15 | "num_committee" : 2, 16 | "votes" : [ 17 | "1:1", 18 | "0:12", 19 | "0:79", 20 | "1:80", 21 | "0:103", 22 | "1:104", 23 | "1:135", 24 | "1:136", 25 | "0:145", 26 | "0:146" 27 | ], 28 | "extensions" : [ 29 | 30 | ], 31 | "voting_account" : "1.2.5", 32 | "memo_key" : "GXC69R784krfXRuFYMuNwhTTnMGPMuCSSng3WPssL6vrXRqTYCLT4", 33 | "num_witness" : 2 34 | }, 35 | "extensions" : [ 36 | 37 | ], 38 | "fee" : { 39 | "asset_id" : "1.3.1", 40 | "amount" : 109 41 | } 42 | } 43 | ] 44 | ], 45 | "ref_block_prefix" : 61153748, 46 | "ref_block_num" : 54375 47 | } 48 | ).toString('hex')); 49 | 50 | 51 | let signWithZ = Signature.signBuffer(Buffer.concat([new Buffer('c2af30ef9340ff81fd61654295e98a1ff04b23189748f86727d0b26b40bb0ff4','hex'),new Buffer('f0cc74a37ede09ba415c0100da0400000000000001d608fe0140420f0000000000010102a596d2dfcdfec6d745bfa6381006870b0aab9dd3bffe382a00f87ff331d48d4803940e48cb1fe1c5975ee9ea5876ccbbe132d69396eebc68201d0198588e44b887684775be6068010030e2fff4995e6ca5eab02d62b7e643e3e2fe8454f809bf40f31991f0463a0685cf3c0d0869b4cafaadb074eaba8c9811460000','hex')]),PrivateKey.fromWif('5J7Yu8zZD5oV9Ex7npmsT3XBbpSdPZPBKBzLLQnXz5JHQVQVfNT')).toHex(); 52 | 53 | let signWithoutZ = Signature.signBuffer(Buffer.concat([new Buffer('c2af30ef9340ff81fd61654295e98a1ff04b23189748f86727d0b26b40bb0ff4','hex'),new Buffer('f0cc74a37ede8949415c0100da0400000000000001d608fe0140420f0000000000010102a596d2dfcdfec6d745bfa6381006870b0aab9dd3bffe382a00f87ff331d48d4803940e48cb1fe1c5975ee9ea5876ccbbe132d69396eebc68201d0198588e44b887684775be6068010030e2fff4995e6ca5eab02d62b7e643e3e2fe8454f809bf40f31991f0463a0685cf3c0d0869b4cafaadb074eaba8c9811460000','hex')]),PrivateKey.fromWif('5J7Yu8zZD5oV9Ex7npmsT3XBbpSdPZPBKBzLLQnXz5JHQVQVfNT')).toHex(); 54 | 55 | 56 | console.log(signWithZ, signWithoutZ) 57 | -------------------------------------------------------------------------------- /lib/chain/src/AccountLogin.js: -------------------------------------------------------------------------------- 1 | import PrivateKey from "../../ecc/src/PrivateKey"; 2 | import key from "../../ecc/src/KeyUtils"; 3 | 4 | import {get, set} from "./state"; 5 | 6 | var _keyCachePriv = {}; 7 | var _keyCachePub = {}; 8 | 9 | class AccountLogin { 10 | 11 | constructor() { 12 | let state = {loggedIn: false, roles: ["active", "owner", "memo"]}; 13 | this.get = get(state); 14 | this.set = set(state); 15 | 16 | this.subs = {}; 17 | } 18 | 19 | addSubscription(cb) { 20 | this.subs[cb] = cb; 21 | } 22 | 23 | setRoles(roles) { 24 | this.set("roles", roles); 25 | } 26 | 27 | generateKeys(accountName, password, roles, prefix) { 28 | var start = new Date().getTime(); 29 | if (!accountName || !password) { 30 | throw new Error("Account name or password required"); 31 | } 32 | if (password.length < 12) { 33 | throw new Error("Password must have at least 12 characters"); 34 | } 35 | 36 | let privKeys = {}; 37 | let pubKeys = {}; 38 | 39 | (roles || this.get("roles")).forEach(role => { 40 | let seed = accountName + role + password; 41 | let pkey = _keyCachePriv[seed] ? _keyCachePriv[seed] : PrivateKey.fromSeed( key.normalize_brainKey(seed) ); 42 | _keyCachePriv[seed] = pkey; 43 | 44 | privKeys[role] = pkey; 45 | pubKeys[role] = _keyCachePub[seed] ? _keyCachePub[seed] : pkey.toPublicKey().toString(prefix); 46 | 47 | _keyCachePub[seed] = pubKeys[role]; 48 | }); 49 | 50 | return {privKeys, pubKeys}; 51 | } 52 | 53 | checkKeys({accountName, password, auths}) { 54 | if (!accountName || !password || !auths) { 55 | throw new Error("checkKeys: Missing inputs"); 56 | } 57 | let hasKey = false; 58 | 59 | for (let role in auths) { 60 | let {privKeys, pubKeys} = this.generateKeys(accountName, password, [role]); 61 | auths[role].forEach(key => { 62 | if (key[0] === pubKeys[role]) { 63 | hasKey = true; 64 | this.set(role, {priv: privKeys[role], pub: pubKeys[role]}); 65 | } 66 | }); 67 | }; 68 | 69 | if (hasKey) { 70 | this.set("name", accountName); 71 | } 72 | 73 | this.set("loggedIn", hasKey); 74 | 75 | return hasKey; 76 | } 77 | 78 | signTransaction(tr) { 79 | let myKeys = {}; 80 | let hasKey = false; 81 | 82 | this.get("roles").forEach(role => { 83 | let myKey = this.get(role); 84 | if (myKey) { 85 | hasKey = true; 86 | console.log("adding signer:", myKey.pub); 87 | tr.add_signer(myKey.priv, myKey.pub); 88 | } 89 | }); 90 | 91 | if (!hasKey) { 92 | throw new Error("You do not have any private keys to sign this transaction"); 93 | } 94 | } 95 | } 96 | 97 | let accountLogin = new AccountLogin(); 98 | 99 | export default accountLogin; 100 | -------------------------------------------------------------------------------- /lib/serializer/src/precision.js: -------------------------------------------------------------------------------- 1 | var _my; 2 | 3 | import v from './SerializerValidation'; 4 | import BigInteger from 'bigi'; 5 | 6 | // _internal is for low-level transaction code 7 | const _internal = { 8 | // Warning: Long operations may over-flow without detection 9 | to_long64(number_or_string, precision, error_info = '') { 10 | v.required(number_or_string, 'number_or_string ' + error_info); 11 | v.required(precision, 'precision ' + error_info); 12 | return v.to_long( 13 | _internal.decimal_precision_string( 14 | number_or_string, 15 | precision, 16 | error_info 17 | ) 18 | ); 19 | }, 20 | 21 | decimal_precision_string(number, precision, error_info = '') { 22 | v.required(number, 'number ' + error_info); 23 | v.required(precision, 'precision ' + error_info); 24 | 25 | var number_string = v.to_string(number); 26 | number_string = number_string.trim(); 27 | precision = v.to_number(precision); 28 | 29 | // remove leading zeros (not suffixing) 30 | var number_parts = number_string.match(/^-?0*([0-9]*)\.?([0-9]*)$/); 31 | if (!number_parts) { 32 | throw new Error(`Invalid number: ${number_string} ${error_info}`); 33 | } 34 | 35 | var sign = number_string.charAt(0) === '-' ? '-' : ''; 36 | var int_part = number_parts[1]; 37 | var decimal_part = number_parts[2]; 38 | if (!decimal_part) { 39 | decimal_part = ''; 40 | } 41 | 42 | // remove trailing zeros 43 | while (/0$/.test(decimal_part)) { 44 | decimal_part = decimal_part.substring(0, decimal_part.length - 1); 45 | } 46 | 47 | var zero_pad_count = precision - decimal_part.length; 48 | if (zero_pad_count < 0) { 49 | throw new Error( 50 | `overflow, up to ${precision} decimals may be used ${error_info}` 51 | ); 52 | } 53 | 54 | if (sign === '-' && !/[1-9]/.test(int_part + decimal_part)) { 55 | sign = ''; 56 | } 57 | if (int_part === '') { 58 | int_part = '0'; 59 | } 60 | for ( 61 | var i = 0; 62 | 0 < zero_pad_count ? i < zero_pad_count : i > zero_pad_count; 63 | 0 < zero_pad_count ? i++ : i++ 64 | ) { 65 | decimal_part += '0'; 66 | } 67 | 68 | return sign + int_part + decimal_part; 69 | } 70 | }; 71 | 72 | _my = { 73 | // Result may be used for int64 types (like transfer amount). Asset's 74 | // precision is used to convert the number to a whole number with an implied 75 | // decimal place. 76 | 77 | // "1.01" with a precision of 2 returns long 101 78 | // See http://cryptocoinjs.com/modules/misc/bigi/#example 79 | 80 | to_bigint64(number_or_string, precision, error_info = '') { 81 | var long = _internal.to_long64(number_or_string, precision, error_info); 82 | return BigInteger(long.toString()); 83 | }, 84 | 85 | // 101 string or long with a precision of 2 returns "1.01" 86 | to_string64(number_or_string, precision, error_info = '') { 87 | v.required(number_or_string, error_info); 88 | v.number(precision, error_info); 89 | var number_long = v.to_long(number_or_string, error_info); 90 | var string64 = _internal.decimal_precision_string( 91 | number_long, 92 | precision, 93 | error_info 94 | ); 95 | v.no_overflow64(string64, error_info); 96 | return string64; 97 | }, 98 | 99 | _internal 100 | }; 101 | 102 | export default _my; 103 | -------------------------------------------------------------------------------- /examples/transfer.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { Apis } from 'gxbjs-ws'; 3 | import { 4 | ChainStore, 5 | FetchChain, 6 | PrivateKey, 7 | TransactionHelper, 8 | Aes, 9 | TransactionBuilder 10 | } from '../lib'; 11 | 12 | var privKey = '5J7Yu8zZD5oV9Ex7npmsT3XBbpSdPZPBKBzLLQnXz5JHQVQVfNT'; 13 | let pKey = PrivateKey.fromWif(privKey); 14 | 15 | Apis.instance('wss://testnet.gxchain.org', true).init_promise.then( 16 | (res) => { 17 | console.log('connected to:', res[0].network_name, 'network'); 18 | 19 | ChainStore.init().then( 20 | () => { 21 | let fromAccount = 'gxb122'; 22 | let memoSender = fromAccount; 23 | let memo = 'Testing transfer from node.js'; 24 | 25 | let toAccount = 'test-net'; 26 | 27 | let sendAmount = { 28 | amount: 1000000, 29 | asset: 'GXC' 30 | }; 31 | 32 | Promise.all([ 33 | FetchChain('getAccount', fromAccount), 34 | FetchChain('getAccount', toAccount), 35 | FetchChain('getAccount', memoSender), 36 | FetchChain('getAsset', sendAmount.asset), 37 | FetchChain('getAsset', sendAmount.asset) 38 | ]) 39 | .then((res) => { 40 | // console.log("got data:", res); 41 | let [fromAccount, toAccount, memoSender, sendAsset, feeAsset] = res; 42 | 43 | // Memos are optional, but if you have one you need to encrypt it here 44 | let memoFromKey = memoSender.getIn(['options', 'memo_key']); 45 | console.log('memo pub key:', memoFromKey); 46 | let memoToKey = toAccount.getIn(['options', 'memo_key']); 47 | let nonce = TransactionHelper.unique_nonce_uint64(); 48 | 49 | let memo_object = { 50 | from: memoFromKey, 51 | to: memoToKey, 52 | nonce, 53 | message: Aes.encrypt_with_checksum(pKey, memoToKey, nonce, memo) 54 | }; 55 | 56 | let tr = new TransactionBuilder(); 57 | 58 | tr.add_type_operation('transfer', { 59 | fee: { 60 | amount: 0, 61 | asset_id: feeAsset.get('id') 62 | }, 63 | from: fromAccount.get('id'), 64 | to: toAccount.get('id'), 65 | amount: { 66 | amount: sendAmount.amount, 67 | asset_id: sendAsset.get('id') 68 | }, 69 | memo: memo_object 70 | }); 71 | 72 | tr.set_required_fees().then( 73 | () => { 74 | tr.add_signer(pKey, pKey.toPublicKey().toPublicKeyString()); 75 | // console.log("serialized transaction:", JSON.stringify(tr.serialize(),null,'\t')); 76 | // tr.broadcast(); 77 | tr.finalize().then(() => { 78 | tr.sign().then(() => { 79 | console.log(JSON.stringify(tr.serialize(), null, '\t')); 80 | }); 81 | }); 82 | }, 83 | (ex) => { 84 | console.error(ex); 85 | } 86 | ); 87 | }) 88 | .catch((ex) => { 89 | console.error(ex); 90 | }); 91 | }, 92 | (ex) => { 93 | console.error(ex); 94 | } 95 | ); 96 | }, 97 | (ex) => { 98 | console.error(ex); 99 | } 100 | ); 101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gxbjs", 3 | "version": "1.5.8", 4 | "description": "Pure JavaScript GXChain library for node.js and browsers.", 5 | "browser": { 6 | "ws": false, 7 | "crypto": false 8 | }, 9 | "scripts": { 10 | "test": "BABEL_ENV=test mocha --compilers js:babel-register --recursive --timeout 5000", 11 | "test:chain": "BABEL_ENV=test mocha --compilers js:babel-register ./test/chain --recursive", 12 | "test:ecc": "BABEL_ENV=test mocha --compilers js:babel-register ./test/ecc --recursive", 13 | "test:serializer": "BABEL_ENV=test mocha --compilers js:babel-register ./test/serializer --recursive", 14 | "test:watch": "npm test -- --watch", 15 | "clean": "rm -rf ./dist/* & rm -rf ./build/* & rm -rf ./es/*", 16 | "prebuild": "npm run clean", 17 | "build": "BABEL_ENV=cjs babel lib -d dist", 18 | "build-es": "BABEL_ENV=es babel lib -d es", 19 | "postbuild": "npm run browserify && npm run browserify_tx_serializer && npm run build-es", 20 | "build:watch": "babel lib -d dist --watch", 21 | "prebrowserify": "rm -rf ./build/*", 22 | "browserify": "browserify --full-paths dist/browser.js --standalone gxb_js -o build/gxbjs.js -d", 23 | "postbrowserify": "uglifyjs --compress --mangle --sequences --drop_console --output build/gxbjs.min.js -- build/gxbjs.js", 24 | "browserify_tx_serializer": "browserify --full-paths dist/tx_serializer.js --standalone serializer -o build/tx_serializer.js -d", 25 | "postbrowserify_tx_serializer": "uglifyjs --compress --mangle --sequences --drop_console --output build/tx_serializer.min.js -- build/tx_serializer.js", 26 | "prepublish": "npm run build", 27 | "doc": "esdoc -c esdoc.json", 28 | "demo": "babel-node" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/gxchain/gxbjs.git" 33 | }, 34 | "author": "David Lan (https://github.com/lanhaoxiang)", 35 | "contributors": [ 36 | "James Calfee (https://github.com/jcalfee/)", 37 | "Daniel Larimer (https://github.com/bytemaster/)", 38 | "Valentine Zavgorodnev (https://github.com/valzav/)", 39 | "David Lan (https://github.com/lanhaoxiang/)" 40 | ], 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/gxchain/gxbjs/issues" 44 | }, 45 | "engines": { 46 | "node": ">= 6.0.0" 47 | }, 48 | "main": "./dist/index.js", 49 | "jsnext:main": "./es/index.js", 50 | "dependencies": { 51 | "bigi": "^1.4.1", 52 | "bs58": "^3.0.0", 53 | "bytebuffer": "^5.0.0", 54 | "create-hash": "^1.1.2", 55 | "create-hmac": "^1.1.4", 56 | "crypto-js": "^3.1.9-1", 57 | "deep-equal": "^1.0.1", 58 | "ecurve": "^1.0.2", 59 | "event-emitter": "^0.3.4", 60 | "gxbjs-ws": "^1.2.1", 61 | "immutable": "^3.7.6", 62 | "secp256k1": "^3.3.0", 63 | "secure-random": "^1.1.1", 64 | "babel-runtime": "^6.26.0" 65 | }, 66 | "devDependencies": { 67 | "@babel/cli": "^7.2.3", 68 | "@babel/core": "^7.3.4", 69 | "@babel/plugin-transform-runtime": "^7.3.4", 70 | "@babel/preset-env": "^7.3.4", 71 | "@babel/register": "^7.0.0", 72 | "assert": "^1.3.0", 73 | "babel-eslint": "^10.0.3", 74 | "browserify": "^16.5.1", 75 | "esdoc": "^1.1.0", 76 | "eslint": "^5.16.0", 77 | "eslint-config-prettier": "^6.1.0", 78 | "eslint-plugin-prettier": "^3.1.0", 79 | "mocha": "^5.2.0", 80 | "prettier": "^1.18.2", 81 | "uglify-js": "^3.4.4" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/chain/src/TransactionHelper.js: -------------------------------------------------------------------------------- 1 | 2 | var helper = {}; 3 | 4 | import secureRandom from 'secure-random'; 5 | 6 | import {Long} from 'bytebuffer'; 7 | 8 | import { Signature } from "../../ecc"; 9 | import { ops } from "../../serializer"; 10 | import {Apis} from "gxbjs-ws"; 11 | 12 | helper.unique_nonce_entropy = null; 13 | helper.unique_nonce_uint64=function() { 14 | var entropy = helper.unique_nonce_entropy = ((() => { 15 | 16 | if (helper.unique_nonce_entropy === null) { 17 | //console.log('... secureRandom.randomUint8Array(1)[0]',secureRandom.randomUint8Array(1)[0]) 18 | return parseInt(secureRandom.randomUint8Array(1)[0]); 19 | } else { 20 | return ++helper.unique_nonce_entropy % 256; 21 | } 22 | })() 23 | ); 24 | var long = Long.fromNumber(Date.now()); 25 | //console.log('unique_nonce_uint64 date\t',ByteBuffer.allocate(8).writeUint64(long).toHex(0)) 26 | //console.log('unique_nonce_uint64 entropy\t',ByteBuffer.allocate(8).writeUint64(Long.fromNumber(entropy)).toHex(0)) 27 | long = long.shiftLeft(8).or(Long.fromNumber(entropy)); 28 | //console.log('unique_nonce_uint64 shift8\t',ByteBuffer.allocate(8).writeUint64(long).toHex(0)) 29 | return long.toString(); 30 | }; 31 | 32 | /* Todo, set fees */ 33 | helper.to_json=function( tr, broadcast = false ) { 34 | return (function(tr, broadcast){ 35 | var tr_object = ops.signed_transaction.toObject(tr); 36 | if (broadcast) { 37 | var net = Apis.instance().network_api(); 38 | console.log('... tr_object', JSON.stringify(tr_object)); 39 | return net.exec("broadcast_transaction", [tr_object]); 40 | } else { 41 | return tr_object; 42 | } 43 | } 44 | )(tr, broadcast); 45 | }; 46 | 47 | helper.signed_tr_json=function(tr, private_keys){ 48 | var tr_buffer = ops.transaction.toBuffer(tr); 49 | tr = ops.transaction.toObject(tr); 50 | tr.signatures = (() => { 51 | var result = []; 52 | for (var i = 0; 0 < private_keys.length ? i < private_keys.length : i > private_keys.length; 0 < private_keys.length ? i++ : i++) { 53 | var private_key = private_keys[i]; 54 | result.push(Signature.signBuffer( tr_buffer, private_key ).toHex()); 55 | } 56 | return result; 57 | })(); 58 | return tr; 59 | }; 60 | 61 | helper.expire_in_min=function(min){ 62 | return Math.round(Date.now() / 1000) + (min*60); 63 | }; 64 | 65 | helper.seconds_from_now=function(timeout_sec){ 66 | return Math.round(Date.now() / 1000) + timeout_sec; 67 | }; 68 | 69 | /** 70 | Print to the console a JSON representation of any object in 71 | @graphene/serializer { types } 72 | */ 73 | helper.template=function(serializer_operation_type_name, debug = {use_default: true, annotate: true}){ 74 | var so = type[serializer_operation_type_name]; 75 | if (!so) { 76 | throw new Error(`unknown serializer_operation_type ${serializer_operation_type_name}`); 77 | } 78 | return so.toObject(undefined, debug); 79 | }; 80 | 81 | helper.new_operation=function(serializer_operation_type_name){ 82 | var so = type[serializer_operation_type_name]; 83 | if (!so) { 84 | throw new Error(`unknown serializer_operation_type ${serializer_operation_type_name}`); 85 | } 86 | var object = so.toObject(undefined, {use_default: true, annotate: true}); 87 | return so.fromObject(object); 88 | }; 89 | 90 | helper.instance=function(ObjectId){ 91 | return ObjectId.substring("0.0.".length); 92 | }; 93 | 94 | export default helper; 95 | -------------------------------------------------------------------------------- /test/chain/TransactionBuilder.js: -------------------------------------------------------------------------------- 1 | // import assert from "assert"; 2 | import { TransactionBuilder, PrivateKey, PublicKey, Signature } from "../../lib"; 3 | import { Apis } from "gxbjs-ws"; 4 | 5 | function accMult(arg1, arg2) { 6 | let m = 0; 7 | let s1 = arg1.toString(); 8 | let s2 = arg2.toString(); 9 | try { 10 | m += s1.split(".")[1].length; 11 | } catch (e) { 12 | } 13 | try { 14 | m += s2.split(".")[1].length; 15 | } catch (e) { 16 | } 17 | return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m); 18 | }; 19 | 20 | describe("TransactionBuilder", () => { 21 | before(function () { 22 | /* use wss://bitshares.openledger.info/ws if no local node is available */ 23 | return Apis.instance("ws://192.168.1.118:28090", true).init_promise.then(function () { 24 | 25 | }); 26 | }); 27 | 28 | it("send transaction signed by signer", () => { 29 | return new Promise((resolve,reject) => { 30 | let tr = new TransactionBuilder(); 31 | tr.add_operation(tr.get_type_operation("transfer", { 32 | "fee": { 33 | "amount": 0, 34 | "asset_id": "1.3.1" 35 | }, 36 | from: "1.2.579", 37 | to: "1.2.492", 38 | amount: { amount: accMult("0.01", Math.pow(10, 5)), asset_id: "1.3.1" } 39 | })); 40 | return Promise.all([tr.update_head_block(), tr.set_required_fees()]).then(() => { 41 | tr.add_signer(PrivateKey.fromWif("5Hpk8pvJxfqfkVx9F7rEjaUM6AyLLkx69jpxf4tTZk3U67wEKSc")); 42 | return tr.broadcast(() => { 43 | resolve(); 44 | }); 45 | }).catch(err=>{ 46 | reject(err); 47 | }); 48 | }); 49 | }); 50 | 51 | it("send transaction signed by signProvider", () => { 52 | async function signer(tr, chain_id) { 53 | return new Promise((resolve) => { 54 | setTimeout(() => { 55 | // test key, peep no crime :P 56 | var private_key = PrivateKey.fromWif("5Hpk8pvJxfqfkVx9F7rEjaUM6AyLLkx69jpxf4tTZk3U67wEKSc"); 57 | var public_key = PublicKey.fromPublicKeyString(private_key.toPublicKey()); 58 | var sig = Signature.signBuffer( 59 | Buffer.concat([new Buffer(chain_id, "hex"), tr.tr_buffer]), 60 | private_key, 61 | public_key 62 | ); 63 | 64 | resolve(sig); 65 | }, 2000); 66 | }); 67 | } 68 | async function signProvider(tr, chain_id) { 69 | const sig = await signer(tr, chain_id); 70 | 71 | // must return array buffer 72 | return [sig.toBuffer()]; 73 | } 74 | 75 | return new Promise((resolve,reject) => { 76 | let tr = new TransactionBuilder(signProvider); 77 | tr.add_operation(tr.get_type_operation("transfer", { 78 | "fee": { 79 | "amount": 0, 80 | "asset_id": "1.3.1" 81 | }, 82 | from: "1.2.579", 83 | to: "1.2.492", 84 | amount: { amount: accMult("0.02", Math.pow(10, 5)), asset_id: "1.3.1" } 85 | })); 86 | return Promise.all([tr.update_head_block(), tr.set_required_fees()]).then(() => { 87 | return tr.broadcast(() => { 88 | resolve(); 89 | }); 90 | }).catch(err=>{ 91 | reject(err); 92 | }); 93 | }); 94 | }); 95 | 96 | 97 | }); 98 | -------------------------------------------------------------------------------- /lib/ecc/src/ecsignature.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; // from https://github.com/bitcoinjs/bitcoinjs-lib 2 | import enforceType from './enforce_types'; 3 | 4 | import BigInteger from 'bigi'; 5 | 6 | function ECSignature(r, s) { 7 | enforceType(BigInteger, r); 8 | enforceType(BigInteger, s); 9 | 10 | this.r = r; 11 | this.s = s; 12 | } 13 | 14 | // Import operations 15 | ECSignature.parseCompact = function(buffer) { 16 | assert.equal(buffer.length, 65, 'Invalid signature length'); 17 | var i = buffer.readUInt8(0) - 27; 18 | 19 | // At most 3 bits 20 | assert.equal(i, i & 7, 'Invalid signature parameter'); 21 | var compressed = !!(i & 4); 22 | 23 | // Recovery param only 24 | i = i & 3; 25 | 26 | var r = BigInteger.fromBuffer(buffer.slice(1, 33)); 27 | var s = BigInteger.fromBuffer(buffer.slice(33)); 28 | 29 | return { 30 | compressed: compressed, 31 | i: i, 32 | signature: new ECSignature(r, s) 33 | }; 34 | }; 35 | 36 | ECSignature.fromDER = function(buffer) { 37 | assert.equal(buffer.readUInt8(0), 0x30, 'Not a DER sequence'); 38 | assert.equal( 39 | buffer.readUInt8(1), 40 | buffer.length - 2, 41 | 'Invalid sequence length' 42 | ); 43 | assert.equal(buffer.readUInt8(2), 0x02, 'Expected a DER integer'); 44 | 45 | var rLen = buffer.readUInt8(3); 46 | assert(rLen > 0, 'R length is zero'); 47 | 48 | var offset = 4 + rLen; 49 | assert.equal(buffer.readUInt8(offset), 0x02, 'Expected a DER integer (2)'); 50 | 51 | var sLen = buffer.readUInt8(offset + 1); 52 | assert(sLen > 0, 'S length is zero'); 53 | 54 | var rB = buffer.slice(4, offset); 55 | var sB = buffer.slice(offset + 2); 56 | offset += 2 + sLen; 57 | 58 | if (rLen > 1 && rB.readUInt8(0) === 0x00) { 59 | assert(rB.readUInt8(1) & 0x80, 'R value excessively padded'); 60 | } 61 | 62 | if (sLen > 1 && sB.readUInt8(0) === 0x00) { 63 | assert(sB.readUInt8(1) & 0x80, 'S value excessively padded'); 64 | } 65 | 66 | assert.equal(offset, buffer.length, 'Invalid DER encoding'); 67 | var r = BigInteger.fromDERInteger(rB); 68 | var s = BigInteger.fromDERInteger(sB); 69 | 70 | assert(r.signum() >= 0, 'R value is negative'); 71 | assert(s.signum() >= 0, 'S value is negative'); 72 | 73 | return new ECSignature(r, s); 74 | }; 75 | 76 | // FIXME: 0x00, 0x04, 0x80 are SIGHASH_* boundary constants, importing Transaction causes a circular dependency 77 | ECSignature.parseScriptSignature = function(buffer) { 78 | var hashType = buffer.readUInt8(buffer.length - 1); 79 | var hashTypeMod = hashType & ~0x80; 80 | 81 | assert(hashTypeMod > 0x00 && hashTypeMod < 0x04, 'Invalid hashType'); 82 | 83 | return { 84 | signature: ECSignature.fromDER(buffer.slice(0, -1)), 85 | hashType: hashType 86 | }; 87 | }; 88 | 89 | // Export operations 90 | ECSignature.prototype.toCompact = function(i, compressed) { 91 | if (compressed) i += 4; 92 | i += 27; 93 | 94 | var buffer = new Buffer(65); 95 | buffer.writeUInt8(i, 0); 96 | 97 | this.r.toBuffer(32).copy(buffer, 1); 98 | this.s.toBuffer(32).copy(buffer, 33); 99 | 100 | return buffer; 101 | }; 102 | 103 | ECSignature.prototype.toDER = function() { 104 | var rBa = this.r.toDERInteger(); 105 | var sBa = this.s.toDERInteger(); 106 | 107 | var sequence = []; 108 | 109 | // INTEGER 110 | sequence.push(0x02, rBa.length); 111 | sequence = sequence.concat(rBa); 112 | 113 | // INTEGER 114 | sequence.push(0x02, sBa.length); 115 | sequence = sequence.concat(sBa); 116 | 117 | // SEQUENCE 118 | sequence.unshift(0x30, sequence.length); 119 | 120 | return new Buffer(sequence); 121 | }; 122 | 123 | ECSignature.prototype.toScriptSignature = function(hashType) { 124 | var hashTypeBuffer = new Buffer(1); 125 | hashTypeBuffer.writeUInt8(hashType, 0); 126 | 127 | return Buffer.concat([this.toDER(), hashTypeBuffer]); 128 | }; 129 | 130 | export default ECSignature; 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GXBJS (gxbjs) 2 | 3 | Pure JavaScript GXChain library for node.js and browsers. Can be used to construct, sign and broadcast transactions in JavaScript, and to easily obtain data from the blockchain via public apis. 4 | 5 | Most of this code was written by [jcalfee](https://github.com/jcalfee), my work was mostly just repackaging to a discrete npm package. 6 | 7 | [![npm version](https://img.shields.io/npm/v/gxbjs.svg?style=flat-square)](https://www.npmjs.com/package/gxbjs) 8 | [![npm downloads](https://img.shields.io/npm/dm/gxbjs.svg?style=flat-square)](https://www.npmjs.com/package/gxbjs) 9 | 10 | 11 | ## Setup 12 | 13 | This library can be obtained through npm: 14 | ``` 15 | npm install gxbjs 16 | ``` 17 | 18 | ## Usage 19 | 20 | Three sub-libraries are included: `ECC`, `Chain` and `Serializer`. Generally only the `ECC` and `Chain` libraries need to be used directly. 21 | 22 | ### Chain 23 | This library provides utility functions to handle blockchain state as well as a login class that can be used for simple login functionality using a specific key seed. 24 | 25 | #### Login 26 | The login class uses the following format for keys: 27 | 28 | ``` 29 | keySeed = accountName + role + password 30 | ``` 31 | 32 | Using this seed, private keys are generated for either the default roles `active, owner, memo`, or as specified. A minimum password length of 12 characters is enforced, but an even longer password is recommended. Three methods are provided: 33 | 34 | ``` 35 | generateKeys(account, password, [roles]) 36 | checkKeys(account, password, auths) 37 | signTransaction(tr) 38 | ``` 39 | 40 | The auths object should contain the auth arrays from the account object. An example is this: 41 | 42 | ``` 43 | { 44 | active: [ 45 | ["GPH5Abm5dCdy3hJ1C5ckXkqUH2Me7dXqi9Y7yjn9ACaiSJ9h8r8mL", 1] 46 | ] 47 | } 48 | ``` 49 | 50 | If checkKeys is successful, you can use signTransaction to sign a TransactionBuilder transaction using the private keys for that account. 51 | 52 | #### State container 53 | The Chain library contains a complete state container called the ChainStore. The ChainStore will automatically configure the `set_subscribe_callback` and handle any incoming state changes appropriately. It uses Immutable.js for storing the state, so all objects are return as immutable objects. It has its own `subscribe` method that can be used to register a callback that will be called whenever a state change happens. 54 | 55 | The ChainStore has several useful methods to retrieve, among other things, objects, assets and accounts using either object ids or asset/account names. These methods are synchronous and will return `undefined` to indicate fetching in progress, and `null` to indicate that the object does not exist. 56 | 57 | ``` 58 | import {Apis} from "gxbjs-ws"; 59 | var {ChainStore} = require("gxbjs"); 60 | 61 | Apis.instance("wss://node.gxb.io", true).init_promise.then((res) => { 62 | console.log("connected to:", res[0].network); 63 | ChainStore.init().then(() => { 64 | ChainStore.subscribe(updateState); 65 | }); 66 | }); 67 | 68 | let dynamicGlobal = null; 69 | function updateState(object) { 70 | dynamicGlobal = ChainStore.getObject("2.1.0"); 71 | console.log("ChainStore object update\n", dynamicGlobal ? dynamicGlobal.toJS() : dynamicGlobal); 72 | } 73 | 74 | ``` 75 | 76 | ### ECC 77 | The ECC library contains all the crypto functions for private and public keys as well as transaction creation/signing. 78 | 79 | #### Private keys 80 | As a quick example, here's how to generate a new private key from a seed (a brainkey for example): 81 | 82 | ``` 83 | var {PrivateKey, key} = require("gxbjs"); 84 | 85 | let seed = "THIS IS A TERRIBLE BRAINKEY SEED WORD SEQUENCE"; 86 | let pkey = PrivateKey.fromSeed( key.normalize_brainKey(seed) ); 87 | 88 | console.log("\nPrivate key:", pkey.toWif()); 89 | console.log("Public key :", pkey.toPublicKey().toString(), "\n"); 90 | ``` 91 | 92 | #### Transactions 93 | TODO transaction signing example 94 | 95 | ## ESDoc (beta) 96 | ```bash 97 | npm i -g esdoc esdoc-es7-plugin 98 | esdoc -c ./esdoc.json 99 | open out/esdoc/index.html 100 | ``` 101 | -------------------------------------------------------------------------------- /examples/dynamicSerializer.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { types } from '../lib/index'; 3 | import ByteBuffer from 'bytebuffer'; 4 | 5 | let abi = { 6 | ____comment: 7 | 'This file was generated by gxb-abigen. DO NOT EDIT - 2018-07-19T11:11:37', 8 | version: 'gxb::abi/1.0', 9 | types: [ 10 | { 11 | new_type_name: 'account_name', 12 | type: 'uint64' 13 | } 14 | ], 15 | structs: [ 16 | { 17 | name: 'hi', 18 | base: '', 19 | fields: [ 20 | { 21 | name: 'user', 22 | type: 'account_name' 23 | } 24 | ] 25 | }, 26 | { 27 | name: 'account', 28 | base: '', 29 | fields: [ 30 | { 31 | name: 'owner', 32 | type: 'account_name' 33 | }, 34 | { 35 | name: 'assets', 36 | type: 'asset[]' 37 | } 38 | ] 39 | }, 40 | { 41 | name: 'lockrule', 42 | base: '', 43 | fields: [ 44 | { 45 | name: 'id', 46 | type: 'uint64' 47 | }, 48 | { 49 | name: 'account_id', 50 | type: 'uint64' 51 | }, 52 | { 53 | name: 'lock_time_point', 54 | type: 'int64' 55 | }, 56 | { 57 | name: 'lock_duration', 58 | type: 'int64' 59 | }, 60 | { 61 | name: 'release_time_point', 62 | type: 'int64' 63 | }, 64 | { 65 | name: 'release_duration', 66 | type: 'int64' 67 | }, 68 | { 69 | name: 'asset_id', 70 | type: 'uint64' 71 | }, 72 | { 73 | name: 'asset_amount', 74 | type: 'int64' 75 | }, 76 | { 77 | name: 'released_amount', 78 | type: 'int64' 79 | } 80 | ] 81 | }, 82 | { 83 | name: 'lockasset', 84 | base: '', 85 | fields: [ 86 | { 87 | name: 'from', 88 | type: 'uint64' 89 | }, 90 | { 91 | name: 'to', 92 | type: 'account_name' 93 | }, 94 | { 95 | name: 'lock_duration', 96 | type: 'int64' 97 | }, 98 | { 99 | name: 'release_duration', 100 | type: 'int64' 101 | } 102 | ] 103 | }, 104 | { 105 | name: 'tryrelease', 106 | base: '', 107 | fields: [ 108 | { 109 | name: 'who', 110 | type: 'uint64' 111 | }, 112 | { 113 | name: 'asset_id', 114 | type: 'uint64' 115 | } 116 | ] 117 | } 118 | ], 119 | actions: [ 120 | { 121 | name: 'lockasset', 122 | type: 'lockasset', 123 | ricardian_contract: '' 124 | }, 125 | { 126 | name: 'tryrelease', 127 | type: 'tryrelease', 128 | ricardian_contract: '' 129 | } 130 | ], 131 | tables: [ 132 | { 133 | name: 'account', 134 | index_type: 'i64', 135 | key_names: ['owner'], 136 | key_types: ['account_name'], 137 | type: 'account' 138 | }, 139 | { 140 | name: 'lockrule', 141 | index_type: 'i64', 142 | key_names: ['id'], 143 | key_types: ['uint64'], 144 | type: 'lockrule' 145 | } 146 | ], 147 | ricardian_clauses: [], 148 | error_messages: [], 149 | abi_extensions: [] 150 | }; 151 | 152 | let serialize = (action, params) => { 153 | let struct = abi.structs.find((s) => s.name === action); 154 | let b = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN); 155 | struct.fields.forEach((f) => { 156 | let value = params[f.name]; 157 | let type = types[f.type]; 158 | if (!type) { 159 | let t = abi.types.find((t) => t.new_type_name === f.type); 160 | if (t) { 161 | type = types[t.type]; 162 | } 163 | } 164 | if (type) { 165 | type.appendByteBuffer(b, value); 166 | } 167 | }); 168 | return new Buffer(b.copy(0, b.offset).toBinary(), 'binary'); 169 | }; 170 | 171 | let result = serialize('hi', { user: '1.2.342' }); 172 | console.log(result.toString('hex')); 173 | -------------------------------------------------------------------------------- /lib/chain/src/ChainTypes.js: -------------------------------------------------------------------------------- 1 | let ChainTypes = {}; 2 | 3 | ChainTypes.reserved_spaces = { 4 | relative_protocol_ids: 0, 5 | protocol_ids: 1, 6 | implementation_ids: 2 7 | }; 8 | 9 | ChainTypes.object_type = { 10 | null: 0, 11 | base: 1, 12 | account: 2, 13 | asset: 3, 14 | force_settlement: 4, 15 | committee_member: 5, 16 | witness: 6, 17 | limit_order: 7, 18 | call_order: 8, 19 | custom: 9, 20 | proposal: 10, 21 | operation_history: 11, 22 | withdraw_permission: 12, 23 | vesting_balance: 13, 24 | worker: 14, 25 | balance: 15, 26 | data_market_category: 16, 27 | free_data_product: 17, 28 | league_data_product: 18, 29 | league: 19, 30 | data_transaction: 20, 31 | pocs: 21, 32 | datasource_copyright: 22, 33 | second_hand_data: 23, 34 | data_transaction_complain: 24, 35 | lock_balance: 25, 36 | trust_node_pledge: 26, 37 | staking: 27 38 | }; 39 | 40 | ChainTypes.impl_object_type = { 41 | global_property: 0, 42 | dynamic_global_property: 1, 43 | index_meta: 2, 44 | asset_dynamic_data: 3, 45 | asset_bitasset_data: 4, 46 | account_balance: 5, 47 | account_statistics: 6, 48 | transaction: 7, 49 | block_summary: 8, 50 | account_transaction_history: 9, 51 | blinded_balance: 10, 52 | chain_property: 11, 53 | witness_schedule: 12, 54 | budget_record: 13 55 | }; 56 | 57 | ChainTypes.vote_type = { 58 | committee: 0, 59 | witness: 1, 60 | worker: 2 61 | }; 62 | 63 | ChainTypes.operations = { 64 | transfer: 0, 65 | limit_order_create: 1, 66 | limit_order_cancel: 2, 67 | call_order_update: 3, 68 | fill_order: 4, 69 | account_create: 5, 70 | account_update: 6, 71 | account_whitelist: 7, 72 | account_upgrade: 8, 73 | account_transfer: 9, 74 | asset_create: 10, 75 | asset_update: 11, 76 | asset_update_bitasset: 12, 77 | asset_update_feed_producers: 13, 78 | asset_issue: 14, 79 | asset_reserve: 15, 80 | asset_fund_fee_pool: 16, 81 | asset_settle: 17, 82 | asset_global_settle: 18, 83 | asset_publish_feed: 19, 84 | witness_create: 20, 85 | witness_update: 21, 86 | proposal_create: 22, 87 | proposal_update: 23, 88 | proposal_delete: 24, 89 | withdraw_permission_create: 25, 90 | withdraw_permission_update: 26, 91 | withdraw_permission_claim: 27, 92 | withdraw_permission_delete: 28, 93 | committee_member_create: 29, 94 | committee_member_update: 30, 95 | committee_member_update_global_parameters: 31, 96 | vesting_balance_create: 32, 97 | vesting_balance_withdraw: 33, 98 | worker_create: 34, 99 | custom: 35, 100 | assert: 36, 101 | balance_claim: 37, 102 | override_transfer: 38, 103 | transfer_to_blind: 39, 104 | blind_transfer: 40, 105 | transfer_from_blind: 41, 106 | asset_settle_cancel: 42, 107 | asset_claim_fees: 43, 108 | fba_distribute: 44, 109 | account_upgrade_merchant: 45, 110 | account_upgrade_datasource: 46, 111 | stale_data_market_category_create: 47, 112 | stale_data_market_category_update: 48, 113 | stale_free_data_product_create: 49, 114 | stale_free_data_product_update: 50, 115 | stale_league_data_product_create: 51, 116 | stale_league_data_product_update: 52, 117 | stale_league_create: 53, 118 | stale_league_update: 54, 119 | data_transaction_create: 55, 120 | data_transaction_update: 56, 121 | data_transaction_pay: 57, 122 | account_upgrade_data_transaction_member: 58, 123 | data_transaction_datasource_upload: 59, 124 | data_transaction_datasource_validate_error: 60, 125 | data_market_category_create: 61, 126 | data_market_category_update: 62, 127 | free_data_product_create: 63, 128 | free_data_product_update: 64, 129 | league_data_product_create: 65, 130 | league_data_product_update: 66, 131 | league_create: 67, 132 | league_update: 68, 133 | datasource_copyright_clear: 69, 134 | data_transaction_complain: 70, 135 | balance_lock: 71, 136 | balance_unlock: 72, 137 | proxy_transfer: 73, 138 | create_contract: 74, 139 | call_contract: 75, 140 | update_contract: 76, 141 | trust_node_pledge_withdraw: 77, 142 | inline_transfer: 78, 143 | inter_contract_call: 79, 144 | staking_create: 80, 145 | staking_update: 81, 146 | staking_claim: 82, 147 | witness_set_commission: 83, 148 | witness_unbanned: 84 149 | }; 150 | 151 | export default ChainTypes; 152 | -------------------------------------------------------------------------------- /lib/chain/src/ChainValidation.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | /** 4 | Account names may contain one or more names separated by a dot. 5 | Each name needs to start with a letter and may contain 6 | numbers, or well placed dashes. 7 | @see is_valid_name graphene/libraries/chain/protocol/account.cpp 8 | */ 9 | let id_regex = /\b\d+\.\d+\.(\d+)\b/; 10 | 11 | var chainValidation = { 12 | is_account_name: function(value, allow_too_short = false) { 13 | var i, label, len, length, ref; 14 | 15 | if (this.is_empty(value)) { 16 | return false; 17 | } 18 | 19 | length = value.length; 20 | 21 | if ((!allow_too_short && length < 3) || length > 63) { 22 | return false; 23 | } 24 | 25 | ref = value.split('.'); 26 | 27 | for (i = 0, len = ref.length; i < len; i++) { 28 | 29 | label = ref[i]; 30 | 31 | if (!(/^[a-z][a-z0-9-]*$/.test(label) && !/--/.test(label) && /[a-z0-9]$/.test(label))) { 32 | return false; 33 | } 34 | 35 | } 36 | return true; 37 | }, 38 | 39 | 40 | is_object_id: function(obj_id) { 41 | if( 'string' != typeof obj_id ) 42 | return false 43 | 44 | let match = id_regex.exec(obj_id) 45 | return (match !== null && obj_id.split(".").length === 3) 46 | }, 47 | 48 | is_empty: function(value) { 49 | return value == null || value.length === 0 50 | }, 51 | 52 | is_account_name_error: function(value, allow_too_short) { 53 | var i, label, len, length, ref, suffix; 54 | if (allow_too_short == null) { 55 | allow_too_short = false; 56 | } 57 | suffix = "Account name should "; 58 | if (this.is_empty(value)) { 59 | return suffix + "not be empty."; 60 | } 61 | length = value.length; 62 | if (!allow_too_short && length < 3) { 63 | return suffix + "be longer."; 64 | } 65 | if (length > 63) { 66 | return suffix + "be shorter."; 67 | } 68 | if (/\./.test(value)) { 69 | suffix = "Each account segment should "; 70 | } 71 | ref = value.split('.'); 72 | for (i = 0, len = ref.length; i < len; i++) { 73 | label = ref[i]; 74 | if (!/^[~a-z]/.test(label)) { 75 | return suffix + "start with a letter."; 76 | } 77 | if (!/^[~a-z0-9-]*$/.test(label)) { 78 | return suffix + "have only letters, digits, or dashes."; 79 | } 80 | if (/--/.test(label)) { 81 | return suffix + "have only one dash in a row."; 82 | } 83 | if (!/[a-z0-9]$/.test(label)) { 84 | return suffix + "end with a letter or digit."; 85 | } 86 | if (!(label.length >= 3)) { 87 | return suffix + "be longer"; 88 | } 89 | } 90 | return null; 91 | }, 92 | 93 | is_cheap_name: function(account_name) { 94 | return /[0-9-]/.test(account_name) || !/[aeiouy]/.test(account_name); 95 | }, 96 | 97 | is_empty_user_input: function(value) { 98 | if (this.is_empty(value)) { return true; } 99 | if ((value+"").trim() === "") { return true; } 100 | return false; 101 | }, 102 | 103 | required: function(value, field_name="") { 104 | if (this.is_empty(value)) { 105 | throw new Error("value required for " + field_name + ": " +value); 106 | } 107 | return value; 108 | }, 109 | 110 | /** @see is_valid_symbol graphene/libraries/chain/protocol/asset_ops.cpp */ 111 | is_valid_symbol_error: function(value) { 112 | var suffix = "Asset name should "; 113 | if (this.is_empty(value)) { return suffix + "not be empty."; } 114 | if (value.split('.').length > 2) { return suffix + "have only one dot."; } 115 | if (value.length < 3) { return suffix + "be longer."; } 116 | if (value.length > 16) { return suffix + "be shorter."; } 117 | if (!/^[A-Z]/.test(value)) { return suffix + "start with a letter"; } 118 | if (!/[A-Z]$/.test(value)) { return suffix + "end with a letter"; } 119 | if (/^[A-Z0-9\.]$/.test(value)) { return suffix + "contain only letters numbers and perhaps a dot."; } 120 | return null; 121 | } 122 | } 123 | 124 | export default chainValidation; 125 | -------------------------------------------------------------------------------- /test/serializer/all_types.js: -------------------------------------------------------------------------------- 1 | import { PrivateKey, PublicKey, Address, Serializer, ops, types } from "../../lib"; 2 | import assert from 'assert'; 3 | 4 | var { 5 | //varint32, 6 | uint8, uint16, uint32, int64, uint64, 7 | string, bytes, bool, array, fixed_array, 8 | protocol_id_type, object_id_type, vote_id, 9 | // future_extensions, 10 | static_variant, map, set, 11 | public_key, address, 12 | time_point_sec, 13 | optional 14 | } = types 15 | 16 | var { asset, account_name_eq_lit_predicate } = ops 17 | 18 | // Must stay in sync with allTypes below. 19 | let AllTypes = new Serializer("all_types", { 20 | uint8, uint16, uint32, int64, uint64, 21 | string, bytes: bytes(1), bool, array: array(uint8), fixed_array: fixed_array(2, uint8), 22 | protocol_id_type: protocol_id_type("account"), object_id_type, //vote_id, 23 | 24 | static_variant: array(static_variant( [asset, account_name_eq_lit_predicate] )), 25 | map: map(uint8, uint8), 26 | set: set(uint8), 27 | 28 | public_key, address, 29 | 30 | time_optional: optional( time_point_sec ), 31 | time_point_sec1: time_point_sec, 32 | time_point_sec2: time_point_sec, 33 | }) 34 | 35 | // Must stay in sync with AllTypes above. 36 | let allTypes = { 37 | 38 | uint8: Math.pow(2,8)-1, uint16: Math.pow(2,16)-1, uint32: Math.pow(2,32)-1, 39 | int64: "9223372036854775807", uint64: "9223372036854775807", 40 | 41 | string: "test", bytes: "ff", bool: true, array: [2, 1], fixed_array: [1, 0], 42 | protocol_id_type: "1.2.2222", object_id_type: "1.1.1", //vote_id: "2:1", 43 | 44 | static_variant: [ [1, {account_id: "1.2.1", name: "abc"}],[0, { amount: "1", asset_id: "1.3.0" }] ], 45 | map: [[4,3], [2,1]], 46 | set: [2,1], 47 | 48 | public_key: PrivateKey.fromSeed("").toPublicKey().toString(), 49 | address: Address.fromPublic(PrivateKey.fromSeed("").toPublicKey()).toString(), 50 | 51 | time_optional: undefined, 52 | time_point_sec1: new Date(), 53 | time_point_sec2: Math.floor(Date.now()/1000), 54 | } 55 | 56 | describe("Serializer", function() { 57 | describe("all types", function() { 58 | 59 | let { toObject, fromObject, toBuffer, fromBuffer } = AllTypes 60 | 61 | toObject = toObject.bind(AllTypes) 62 | fromObject = fromObject.bind(AllTypes) 63 | toBuffer = toBuffer.bind(AllTypes) 64 | fromBuffer = fromBuffer.bind(AllTypes) 65 | 66 | it("from object", function() { 67 | assert(fromObject(allTypes), "serializable" ) 68 | assert(fromObject(fromObject(allTypes)), "non-serializable") 69 | }) 70 | 71 | it("to object", function() { 72 | assert(toObject(allTypes), "serializable" ) 73 | assert.deepEqual(toObject(allTypes), toObject(allTypes), "serializable (single to)" ) 74 | assert.deepEqual(toObject(toObject(allTypes)), toObject(allTypes), "serializable (double to)" ) 75 | assert.deepEqual(toObject(fromObject(allTypes)), toObject(allTypes), "non-serializable" ) 76 | assert.deepEqual(toObject(fromObject(fromObject(allTypes))), toObject(allTypes), "non-serializable (double from)") 77 | }) 78 | 79 | it("to buffer", function(){ 80 | assert(toBuffer(allTypes), "serializable" ) 81 | assert(toBuffer(fromObject(allTypes)), "non-serializable") 82 | assert.equal( 83 | toBuffer( allTypes ).toString("hex"), // serializable 84 | toBuffer( fromObject( allTypes )).toString("hex"), // non-serializable 85 | "serializable and non-serializable" 86 | ) 87 | }) 88 | 89 | it("from buffer", function() { 90 | assert.deepEqual(toObject(fromBuffer(toBuffer(allTypes))), toObject(allTypes), "serializable" ) 91 | assert.deepEqual(toObject(fromBuffer(toBuffer(fromObject(allTypes)))), toObject(allTypes), "non-serializable" ) 92 | }) 93 | 94 | it("template", function() { 95 | assert(toObject(allTypes, {use_default: true})) 96 | assert(toObject(allTypes, {annotate: true})) 97 | assert(toObject({}, {use_default: true})) 98 | assert(toObject({}, {use_default: true, annotate: true})) 99 | 100 | }) 101 | 102 | // keep last 103 | // it("visual check", function() { 104 | // console.log(toObject(fromObject(allTypes))) 105 | // }) 106 | 107 | }) 108 | }); 109 | -------------------------------------------------------------------------------- /test/ecc/KeyFormats.js: -------------------------------------------------------------------------------- 1 | import { Aes, PrivateKey, PublicKey, Address } from "../../lib"; 2 | import assert from "assert"; 3 | 4 | var test = function(key) { 5 | describe("ECC", function() { 6 | describe("Key Formats", function() { 7 | 8 | it("Calculates public key from private key", function() { 9 | var private_key = PrivateKey.fromHex(key.private_key); 10 | var public_key = private_key.toPublicKey(); 11 | assert.equal(key.public_key, public_key.toPublicKeyString()); 12 | }); 13 | 14 | it("Create BTS short address", function() { 15 | var public_key = PublicKey.fromPublicKeyString(key.public_key); 16 | assert.equal(key.bts_address, public_key.toAddressString()); 17 | }) 18 | 19 | it("Blockchain Address", function() { 20 | var public_key = PublicKey.fromPublicKeyString(key.public_key); 21 | assert.equal(key.blockchain_address, public_key.toBlockchainAddress().toString('hex')); 22 | }); 23 | 24 | it("BTS public key import / export", function() { 25 | var public_key = PublicKey.fromPublicKeyString(key.public_key); 26 | assert.equal(key.public_key, public_key.toPublicKeyString()); 27 | }); 28 | 29 | it("PTS", function() { 30 | var private_key = PrivateKey.fromHex(key.private_key); 31 | var public_key = private_key.toPublicKey(); 32 | assert.equal(key.pts_address, public_key.toPtsAddy()); 33 | }); 34 | 35 | it("To WIF", function() { 36 | var private_key = PrivateKey.fromHex(key.private_key); 37 | assert.equal(key.private_key_WIF_format, private_key.toWif()); 38 | }); 39 | 40 | it("From WIF", function() { 41 | var private_key = PrivateKey.fromWif(key.private_key_WIF_format); 42 | assert.equal(private_key.toHex(), key.private_key); 43 | }); 44 | 45 | it("Calc public key", function() { 46 | var private_key = PrivateKey.fromHex(key.private_key); 47 | var public_key = private_key.toPublicKey(); 48 | assert.equal(key.bts_address, public_key.toAddressString()); 49 | }); 50 | 51 | it("Decrypt private key", function() { 52 | var aes = Aes.fromSeed("Password00"); 53 | var d = aes.decryptHex(key.encrypted_private_key); 54 | assert.equal(key.private_key, d); 55 | }); 56 | 57 | it("BTS/BTC uncompressed", function() { 58 | var public_key = PublicKey.fromPublicKeyString(key.public_key); 59 | var address = Address.fromPublic(public_key, false, 0); 60 | assert.equal(key.Uncompressed_BTC, address.toString()); 61 | }); 62 | 63 | it("BTS/BTC compressed", function() { 64 | var public_key = PublicKey.fromPublicKeyString(key.public_key); 65 | var address = Address.fromPublic(public_key, true, 0); 66 | assert.equal(key.Compressed_BTC, address.toString()); 67 | }); 68 | 69 | it("BTS/PTS uncompressed", function() { 70 | var public_key = PublicKey.fromPublicKeyString(key.public_key); 71 | var address = Address.fromPublic(public_key, false, 56); 72 | assert.equal(key.Uncompressed_PTS, address.toString()); 73 | }); 74 | 75 | it("BTS/PTS compressed", function() { 76 | var public_key = PublicKey.fromPublicKeyString(key.public_key); 77 | var address = Address.fromPublic(public_key, true, 56); 78 | assert.equal(key.Compressed_PTS, address.toString()); 79 | }); 80 | 81 | it("Null public key to/from buffer", function() { 82 | var public_key = PublicKey.fromStringOrThrow(key.null_public_key); 83 | var buffer = public_key.toBuffer(); 84 | var new_public_key = PublicKey.fromBuffer(buffer); 85 | assert.equal(new_public_key.toPublicKeyString(), key.null_public_key); 86 | }); 87 | }); 88 | }); 89 | }; 90 | 91 | test({ 92 | // delegate0 93 | // sourced from: ./bitshares/programs/utils/bts_create_key 94 | public_key: "GPH7jDPoMwyjVH5obFmqzFNp4Ffp7G2nvC7FKFkrMBpo7Sy4uq5Mj", 95 | private_key: "20991828d456b389d0768ed7fb69bf26b9bb87208dd699ef49f10481c20d3e18", 96 | private_key_WIF_format: "5J4eFhjREJA7hKG6KcvHofHMXyGQZCDpQE463PAaKo9xXY6UDPq", 97 | bts_address: "GPH8DvGQqzbgCR5FHiNsFf8kotEXr8VKD3mR", 98 | pts_address: "Po3mqkgMzBL4F1VXJArwQxeWf3fWEpxUf3", 99 | encrypted_private_key: "5e1ae410919c450dce1c476ae3ed3e5fe779ad248081d85b3dcf2888e698744d0a4b60efb7e854453bec3f6883bcbd1d", 100 | blockchain_address: "4f3a560442a05e4fbb257e8dc5859b736306bace", 101 | // https://github.com/BitShares/bitshares/blob/2602504998dcd63788e106260895769697f62b07/libraries/wallet/wallet_db.cpp#L103-L108 102 | Uncompressed_BTC: "GPHLAFmEtM8as1mbmjVcj5dphLdPguXquimn", 103 | Compressed_BTC: "GPHANNTSEaUviJgWLzJBersPmyFZBY4jJETY", 104 | Uncompressed_PTS: "GPHEgj7RM6FBwSoccGaESJLC3Mi18785bM3T", 105 | Compressed_PTS: "GPHD5rYtofD6D4UHJH6mo953P5wpBfMhdMEi", 106 | null_public_key: "GPH1111111111111111111111111111111114T1Anm" 107 | }); 108 | -------------------------------------------------------------------------------- /test/serializer/types_test.js: -------------------------------------------------------------------------------- 1 | import Convert from '../../lib/serializer/src/convert'; 2 | import {Long} from 'bytebuffer'; 3 | 4 | import assert from 'assert'; 5 | import p from '../../lib/serializer/src/precision'; 6 | import th from './test_helper'; 7 | 8 | import { is } from "immutable"; 9 | import { PublicKey, PrivateKey, types } from "../../lib"; 10 | import { ChainConfig } from "gxbjs-ws"; 11 | 12 | describe("types", function() { 13 | 14 | it("vote_id",function() { 15 | var toHex=function(id){ 16 | var vote = types.vote_id.fromObject(id); 17 | return Convert(types.vote_id).toHex(vote); 18 | }; 19 | assert.equal("ff000000", toHex("255:0")); 20 | assert.equal("00ffffff", toHex("0:"+0xffffff)); 21 | var out_of_range=function(id){ 22 | try { 23 | toHex(id); 24 | return assert(false, 'should have been out of range'); 25 | } catch (e) { 26 | return assert(e.message.indexOf('out of range') !== -1); 27 | } 28 | }; 29 | out_of_range("0:"+(0xffffff+1)); 30 | out_of_range("256:0"); 31 | 32 | }); 33 | 34 | it("set sort", function() { 35 | var bool_set = types.set(types.bool); 36 | // Note, 1,0 sorts to 0,1 37 | assert.equal("020001", Convert(bool_set).toHex([1,0])); 38 | th.error("duplicate (set)", function() { return Convert(bool_set).toHex([1,1]); }); 39 | 40 | }); 41 | 42 | it("string sort", function() { 43 | var setType = types.set(types.string); 44 | var set = setType.fromObject(["a","z","m"]) 45 | var setObj = setType.toObject(set) 46 | assert.deepEqual(["a","m","z"], setObj, "not sorted") 47 | }); 48 | 49 | it("map sort", function() { 50 | var bool_map = types.map(types.bool, types.bool); 51 | // 1,1 0,0 sorts to 0,0 1,1 52 | assert.equal("0200000101", Convert(bool_map).toHex([[1,1],[0,0]])); 53 | th.error("duplicate (map)", function() { return Convert(bool_map).toHex([[1,1],[1,1]]); }); 54 | }) 55 | 56 | before(function() { 57 | ChainConfig.setPrefix("TEST"); 58 | }); 59 | 60 | it("public_key sort", function() { 61 | let mapType = types.map(types.public_key, types.uint16) 62 | let map = mapType.fromObject([//not sorted 63 | ["TEST6FHYdi17RhcUXJZr5fxZm1wvVCpXPekiHeAEwRHSEBmiR3yceK",0], 64 | ["TEST5YdgWfAejDdSuq55xfguqFTtbRKLi2Jcz1YtTsCzYgdUYXs92c",0], 65 | ["TEST7AGnzGCAGVfFnyvPziN67mfuHx9rx89r2zVoRGW1Aawim1f3Qt",0], 66 | ]) 67 | let mapObject = mapType.toObject(map) 68 | assert.deepEqual(mapObject, [ // sorted (witness_node sorts assending by "address" (not pubkey)) 69 | ["TEST7AGnzGCAGVfFnyvPziN67mfuHx9rx89r2zVoRGW1Aawim1f3Qt",0], 70 | ["TEST5YdgWfAejDdSuq55xfguqFTtbRKLi2Jcz1YtTsCzYgdUYXs92c",0], 71 | ["TEST6FHYdi17RhcUXJZr5fxZm1wvVCpXPekiHeAEwRHSEBmiR3yceK",0], 72 | ]) 73 | }) 74 | 75 | 76 | 77 | it("type_id sort", function() { 78 | // map (protocol_id_type "account"), (uint16) 79 | let t = types.map(types.protocol_id_type("account"), types.uint16); 80 | assert.deepEqual( t.fromObject([[1,1],[0,0]]), [[0,0],[1,1]], 'did not sort' ) 81 | assert.deepEqual( t.fromObject([[0,0],[1,1]]), [[0,0],[1,1]], 'did not sort' ) 82 | }); 83 | 84 | it("precision number strings", function() { 85 | var check=function(input_string, precision, output_string){ 86 | return assert.equal( 87 | output_string, 88 | p._internal.decimal_precision_string( 89 | input_string, 90 | precision 91 | ) 92 | ); 93 | }; 94 | 95 | check( 96 | "12345678901234567890123456789012345678901234567890.12345",5, 97 | "1234567890123456789012345678901234567890123456789012345" 98 | ); 99 | check("", 0, "0"); 100 | check("0", 0, "0"); 101 | check("-0", 0, "0"); 102 | check("-00", 0, "0"); 103 | check("-0.0", 0, "0"); 104 | check("-", 0, "0"); 105 | check("1", 0, "1"); 106 | check("11", 0, "11"); 107 | 108 | overflow(function(){ return check(".1", 0, ""); }); 109 | overflow(function(){ return check("-.1", 0, ""); }); 110 | overflow(function(){ return check("0.1", 0, ""); }); 111 | overflow(function(){ return check("1.1", 0, ""); }); 112 | overflow(function(){ return check("1.11", 1, ""); }); 113 | 114 | check("", 1, "00"); 115 | check("1", 1, "10"); 116 | check("1.1", 1, "11"); 117 | check("-1", 1, "-10"); 118 | check("-1.1", 1, "-11"); 119 | 120 | }); 121 | 122 | return it("precision number long", function() { 123 | var _precision; 124 | assert.equal( 125 | Long.MAX_VALUE.toString(), 126 | p.to_bigint64( 127 | Long.MAX_VALUE.toString(), _precision = 0 128 | ).toString(), 129 | "to_bigint64 MAX_VALUE mismatch" 130 | ); 131 | 132 | // Long.MAX_VALUE.toString() == 9223372036854775807 133 | // Long.MAX_VALUE.toString() +1 9223372036854775808 134 | overflow(function(){ return p.to_bigint64( 135 | '9223372036854775808', _precision = 0 136 | ); 137 | }); 138 | 139 | assert.equal("0", p.to_string64(Long.ZERO, 0)); 140 | assert.equal("00", p.to_string64(Long.ZERO, 1)); 141 | 142 | overflow(function(){ return p.to_bigint64( 143 | '92233720368547758075', _precision = 1 144 | ); 145 | }); 146 | 147 | }); 148 | }); 149 | 150 | var overflow = function(f){ return th.error("overflow", f); }; 151 | -------------------------------------------------------------------------------- /lib/ecc/src/PublicKey.js: -------------------------------------------------------------------------------- 1 | import BigInteger from 'bigi'; 2 | import { Point, getCurveByName } from 'ecurve'; 3 | const secp256k1 = getCurveByName('secp256k1'); 4 | import { encode, decode } from 'bs58'; 5 | import { sha256, sha512, ripemd160 } from './hash'; 6 | import { ChainConfig } from 'gxbjs-ws'; 7 | import assert from 'assert'; 8 | import deepEqual from 'deep-equal'; 9 | import ByteBuffer from 'bytebuffer'; 10 | 11 | const { G, n } = secp256k1; 12 | 13 | class PublicKey { 14 | /** @param {Point} public key */ 15 | constructor(Q) { 16 | this.Q = Q; 17 | } 18 | 19 | static fromBinary(bin) { 20 | return PublicKey.fromBuffer(new Buffer(bin, 'binary')); 21 | } 22 | 23 | static fromBuffer(buffer) { 24 | if ( 25 | buffer.toString('hex') === 26 | '000000000000000000000000000000000000000000000000000000000000000000' 27 | ) 28 | return new PublicKey(null); 29 | return new PublicKey(Point.decodeFrom(secp256k1, buffer)); 30 | } 31 | 32 | toBuffer(compressed = this.Q ? this.Q.compressed : null) { 33 | if (this.Q === null) 34 | return new Buffer( 35 | '000000000000000000000000000000000000000000000000000000000000000000', 36 | 'hex' 37 | ); 38 | return this.Q.getEncoded(compressed); 39 | } 40 | 41 | static fromPoint(point) { 42 | return new PublicKey(point); 43 | } 44 | 45 | toUncompressed() { 46 | var buf = this.Q.getEncoded(false); 47 | var point = Point.decodeFrom(secp256k1, buf); 48 | return PublicKey.fromPoint(point); 49 | } 50 | 51 | /** bts::blockchain::address (unique but not a full public key) */ 52 | toBlockchainAddress() { 53 | var pub_buf = this.toBuffer(); 54 | var pub_sha = sha512(pub_buf); 55 | return ripemd160(pub_sha); 56 | } 57 | 58 | /** Alias for {@link toPublicKeyString} */ 59 | toString(address_prefix = ChainConfig.address_prefix) { 60 | return this.toPublicKeyString(address_prefix); 61 | } 62 | 63 | /** 64 | Full public key 65 | {return} string 66 | */ 67 | toPublicKeyString(address_prefix = ChainConfig.address_prefix) { 68 | var pub_buf = this.toBuffer(); 69 | var checksum = ripemd160(pub_buf); 70 | var addy = Buffer.concat([pub_buf, checksum.slice(0, 4)]); 71 | return address_prefix + encode(addy); 72 | } 73 | 74 | /** 75 | @arg {string} public_key - like GPHXyz... 76 | @arg {string} address_prefix - like GPH 77 | @return PublicKey or `null` (if the public_key string is invalid) 78 | */ 79 | static fromPublicKeyString( 80 | public_key, 81 | address_prefix = ChainConfig.address_prefix 82 | ) { 83 | try { 84 | return PublicKey.fromStringOrThrow(public_key, address_prefix); 85 | } catch (e) { 86 | return null; 87 | } 88 | } 89 | 90 | /** 91 | @arg {string} public_key - like GPHXyz... 92 | @arg {string} address_prefix - like GPH 93 | @throws {Error} if public key is invalid 94 | @return PublicKey 95 | */ 96 | static fromStringOrThrow( 97 | public_key, 98 | address_prefix = ChainConfig.address_prefix 99 | ) { 100 | var prefix = public_key.slice(0, address_prefix.length); 101 | assert.equal( 102 | address_prefix, 103 | prefix, 104 | `Expecting key to begin with ${address_prefix}, instead got ${prefix}` 105 | ); 106 | public_key = public_key.slice(address_prefix.length); 107 | 108 | public_key = new Buffer(decode(public_key), 'binary'); 109 | var checksum = public_key.slice(-4); 110 | public_key = public_key.slice(0, -4); 111 | var new_checksum = ripemd160(public_key); 112 | new_checksum = new_checksum.slice(0, 4); 113 | var isEqual = deepEqual(checksum, new_checksum); //, 'Invalid checksum' 114 | if (!isEqual) { 115 | throw new Error('Checksum did not match'); 116 | } 117 | return PublicKey.fromBuffer(public_key); 118 | } 119 | 120 | toAddressString(address_prefix = ChainConfig.address_prefix) { 121 | var pub_buf = this.toBuffer(); 122 | var pub_sha = sha512(pub_buf); 123 | var addy = ripemd160(pub_sha); 124 | var checksum = ripemd160(addy); 125 | addy = Buffer.concat([addy, checksum.slice(0, 4)]); 126 | return address_prefix + encode(addy); 127 | } 128 | 129 | toPtsAddy() { 130 | var pub_buf = this.toBuffer(); 131 | var pub_sha = sha256(pub_buf); 132 | var addy = ripemd160(pub_sha); 133 | addy = Buffer.concat([new Buffer([0x38]), addy]); //version 56(decimal) 134 | 135 | var checksum = sha256(addy); 136 | checksum = sha256(checksum); 137 | 138 | addy = Buffer.concat([addy, checksum.slice(0, 4)]); 139 | return encode(addy); 140 | } 141 | 142 | child(offset) { 143 | assert(Buffer.isBuffer(offset), 'Buffer required: offset'); 144 | assert.equal(offset.length, 32, 'offset length'); 145 | 146 | offset = Buffer.concat([this.toBuffer(), offset]); 147 | offset = sha256(offset); 148 | 149 | let c = BigInteger.fromBuffer(offset); 150 | 151 | if (c.compareTo(n) >= 0) 152 | throw new Error('Child offset went out of bounds, try again'); 153 | 154 | let cG = G.multiply(c); 155 | let Qprime = this.Q.add(cG); 156 | 157 | if (secp256k1.isInfinity(Qprime)) 158 | throw new Error('Child offset derived to an invalid key, try again'); 159 | 160 | return PublicKey.fromPoint(Qprime); 161 | } 162 | 163 | /* */ 164 | 165 | toByteBuffer() { 166 | var b = new ByteBuffer( 167 | ByteBuffer.DEFAULT_CAPACITY, 168 | ByteBuffer.LITTLE_ENDIAN 169 | ); 170 | this.appendByteBuffer(b); 171 | return b.copy(0, b.offset); 172 | } 173 | 174 | static fromHex(hex) { 175 | return PublicKey.fromBuffer(new Buffer(hex, 'hex')); 176 | } 177 | 178 | toHex() { 179 | return this.toBuffer().toString('hex'); 180 | } 181 | 182 | static fromPublicKeyStringHex(hex) { 183 | return PublicKey.fromPublicKeyString(new Buffer(hex, 'hex')); 184 | } 185 | 186 | /* */ 187 | } 188 | 189 | export default PublicKey; 190 | -------------------------------------------------------------------------------- /lib/ecc/src/signature.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { calcPubKeyRecoveryParam, recoverPubKey, sign, verify } from './ecdsa'; 3 | import { sha256 } from './hash'; 4 | import { getCurveByName } from 'ecurve'; 5 | import assert from 'assert'; 6 | import BigInteger from 'bigi'; 7 | import PublicKey from './PublicKey'; 8 | import ByteBuffer from 'bytebuffer'; 9 | // import ECSignature from './ecsignature'; 10 | 11 | var secp256k1 = getCurveByName('secp256k1'); 12 | var secp256k1Lib = require('secp256k1'); 13 | 14 | class Signature { 15 | constructor(r1, s1, i1) { 16 | this.r = r1; 17 | this.s = s1; 18 | this.i = i1; 19 | assert.equal(this.r != null, true, 'Missing parameter'); 20 | assert.equal(this.s != null, true, 'Missing parameter'); 21 | assert.equal(this.i != null, true, 'Missing parameter'); 22 | } 23 | 24 | static fromBuffer(buf) { 25 | var i, r, s; 26 | assert.equal(buf.length, 65, 'Invalid signature length'); 27 | i = buf.readUInt8(0); 28 | assert.equal(i - 27, (i - 27) & 7, 'Invalid signature parameter'); 29 | r = BigInteger.fromBuffer(buf.slice(1, 33)); 30 | s = BigInteger.fromBuffer(buf.slice(33)); 31 | return new Signature(r, s, i); 32 | } 33 | 34 | toBuffer() { 35 | var buf; 36 | buf = new Buffer(65); 37 | buf.writeUInt8(this.i, 0); 38 | this.r.toBuffer(32).copy(buf, 1); 39 | this.s.toBuffer(32).copy(buf, 33); 40 | return buf; 41 | } 42 | 43 | recoverPublicKeyFromBuffer(buffer) { 44 | return this.recoverPublicKey(sha256(buffer)); 45 | } 46 | 47 | /** 48 | @return {PublicKey} 49 | */ 50 | recoverPublicKey(sha256_buffer) { 51 | let Q, e, i; 52 | e = BigInteger.fromBuffer(sha256_buffer); 53 | i = this.i; 54 | i -= 27; 55 | i = i & 3; 56 | Q = recoverPubKey(secp256k1, e, this, i); 57 | return PublicKey.fromPoint(Q); 58 | } 59 | 60 | /** 61 | * signature malleability check https://github.com/bitshares/bitshares1-core/issues/1129 62 | * @returns {boolean} 63 | */ 64 | isCanonical() { 65 | let r = this.r.toBuffer(32); 66 | let s = this.s.toBuffer(32); 67 | return ( 68 | !(r[0] & 0x80) && 69 | !(r[0] == 0 && !(r[1] & 0x80)) && 70 | !(s[0] & 0x80) && 71 | !(s[0] == 0 && !(s[1] & 0x80)) 72 | ); 73 | } 74 | 75 | /** 76 | @param {Buffer} buf 77 | @param {PrivateKey} private_key 78 | @return {Signature} 79 | */ 80 | static signBuffer(buf, private_key) { 81 | var _hash = sha256(buf); 82 | let signature = Signature.signBufferSha256(_hash, private_key); 83 | let times = 0; 84 | while (!signature.isCanonical()) { 85 | signature = Signature.signBufferSha256(_hash, private_key); 86 | if (times++ > 10) { 87 | console.log('WARN: ECDSA tried', times, 'times'); 88 | } 89 | } 90 | return signature; 91 | } 92 | 93 | /** Sign a buffer of exactally 32 bytes in size (sha256(text)) 94 | @param {Buffer} buf - 32 bytes binary 95 | @param {PrivateKey} private_key 96 | @return {Signature} 97 | */ 98 | static signBufferSha256(buf_sha256, private_key) { 99 | if (buf_sha256.length !== 32 || !Buffer.isBuffer(buf_sha256)) 100 | throw new Error('buf_sha256: 32 byte buffer requred'); 101 | var der, e, ecsignature, i, lenR, lenS, nonce; 102 | i = null; 103 | nonce = 0; 104 | e = BigInteger.fromBuffer(buf_sha256); 105 | // eslint-disable-next-line no-constant-condition 106 | while (true) { 107 | ecsignature = sign(secp256k1, buf_sha256, private_key.d, nonce++); 108 | der = ecsignature.toDER(); 109 | 110 | lenR = der[3]; 111 | lenS = der[5 + lenR]; 112 | if (lenR === 32 && lenS === 32) { 113 | i = calcPubKeyRecoveryParam( 114 | secp256k1, 115 | e, 116 | ecsignature, 117 | private_key.toPublicKey().Q 118 | ); 119 | i += 4; // compressed 120 | i += 27; // compact // 24 or 27 :( forcing odd-y 2nd key candidate) 121 | break; 122 | } 123 | if (nonce % 10 === 0) { 124 | console.log('WARN: ' + nonce + ' attempts to find canonical signature'); 125 | } 126 | } 127 | return new Signature(ecsignature.r, ecsignature.s, i); 128 | } 129 | 130 | static sign(string, private_key) { 131 | return Signature.signBuffer(new Buffer(string), private_key); 132 | } 133 | 134 | /** 135 | @param {Buffer} un-hashed 136 | @param {./PublicKey} 137 | @return {boolean} 138 | */ 139 | verifyBuffer(buf, public_key) { 140 | var _hash = sha256(buf); 141 | return this.verifyHashV2(_hash, public_key); 142 | // return this.verifyHash(_hash, public_key); 143 | } 144 | 145 | verifyHash(hash, public_key) { 146 | assert.equal( 147 | hash.length, 148 | 32, 149 | 'A SHA 256 should be 32 bytes long, instead got ' + hash.length 150 | ); 151 | return verify( 152 | secp256k1, 153 | hash, 154 | { 155 | r: this.r, 156 | s: this.s 157 | }, 158 | public_key.Q 159 | ); 160 | } 161 | 162 | verifyHashV2(hash, public_key) { 163 | assert.equal( 164 | hash.length, 165 | 32, 166 | 'A SHA 256 should be 32 bytes long, instead got ' + hash.length 167 | ); 168 | return secp256k1Lib.verify( 169 | hash, 170 | this.toBuffer().slice(1), 171 | public_key.toBuffer() 172 | ); 173 | } 174 | 175 | /* */ 176 | 177 | toByteBuffer() { 178 | var b; 179 | b = new ByteBuffer(ByteBuffer.DEFAULT_CAPACITY, ByteBuffer.LITTLE_ENDIAN); 180 | this.appendByteBuffer(b); 181 | return b.copy(0, b.offset); 182 | } 183 | 184 | static fromHex(hex) { 185 | return Signature.fromBuffer(new Buffer(hex, 'hex')); 186 | } 187 | 188 | toHex() { 189 | return this.toBuffer().toString('hex'); 190 | } 191 | 192 | static signHex(hex, private_key) { 193 | var buf; 194 | buf = new Buffer(hex, 'hex'); 195 | return Signature.signBuffer(buf, private_key); 196 | } 197 | 198 | verifyHex(hex, public_key) { 199 | var buf; 200 | buf = new Buffer(hex, 'hex'); 201 | return this.verifyBuffer(buf, public_key); 202 | } 203 | } 204 | 205 | export default Signature; 206 | -------------------------------------------------------------------------------- /lib/ecc/src/ecdsa.js: -------------------------------------------------------------------------------- 1 | import assert from 'assert'; // from github.com/bitcoinjs/bitcoinjs-lib from github.com/cryptocoinjs/ecdsa 2 | import { sha256, HmacSHA256 } from './hash'; 3 | import enforceType from './enforce_types'; 4 | 5 | import BigInteger from 'bigi'; 6 | import ECSignature from './ecsignature'; 7 | 8 | // https://tools.ietf.org/html/rfc6979#section-3.2 9 | function deterministicGenerateK(curve, hash, d, checkSig, nonce) { 10 | enforceType('Buffer', hash); 11 | enforceType(BigInteger, d); 12 | 13 | if (nonce) { 14 | hash = sha256(Buffer.concat([hash, new Buffer(nonce)])); 15 | } 16 | 17 | // sanity check 18 | assert.equal(hash.length, 32, 'Hash must be 256 bit'); 19 | 20 | var x = d.toBuffer(32); 21 | var k = new Buffer(32); 22 | var v = new Buffer(32); 23 | 24 | // Step B 25 | v.fill(1); 26 | 27 | // Step C 28 | k.fill(0); 29 | 30 | // Step D 31 | k = HmacSHA256(Buffer.concat([v, new Buffer([0]), x, hash]), k); 32 | 33 | // Step E 34 | v = HmacSHA256(v, k); 35 | 36 | // Step F 37 | k = HmacSHA256(Buffer.concat([v, new Buffer([1]), x, hash]), k); 38 | 39 | // Step G 40 | v = HmacSHA256(v, k); 41 | 42 | // Step H1/H2a, ignored as tlen === qlen (256 bit) 43 | // Step H2b 44 | v = HmacSHA256(v, k); 45 | 46 | var T = BigInteger.fromBuffer(v); 47 | 48 | // Step H3, repeat until T is within the interval [1, n - 1] 49 | while (T.signum() <= 0 || T.compareTo(curve.n) >= 0 || !checkSig(T)) { 50 | k = HmacSHA256(Buffer.concat([v, new Buffer([0])]), k); 51 | v = HmacSHA256(v, k); 52 | 53 | // Step H1/H2a, again, ignored as tlen === qlen (256 bit) 54 | // Step H2b again 55 | v = HmacSHA256(v, k); 56 | 57 | T = BigInteger.fromBuffer(v); 58 | } 59 | 60 | return T; 61 | } 62 | 63 | function sign(curve, hash, d, nonce) { 64 | var e = BigInteger.fromBuffer(hash); 65 | var n = curve.n; 66 | var G = curve.G; 67 | 68 | var r, s; 69 | // eslint-disable-next-line no-unused-vars 70 | var k = deterministicGenerateK( 71 | curve, 72 | hash, 73 | d, 74 | function(k) { 75 | // find canonically valid signature 76 | var Q = G.multiply(k); 77 | 78 | if (curve.isInfinity(Q)) return false; 79 | 80 | r = Q.affineX.mod(n); 81 | if (r.signum() === 0) return false; 82 | 83 | s = k 84 | .modInverse(n) 85 | .multiply(e.add(d.multiply(r))) 86 | .mod(n); 87 | if (s.signum() === 0) return false; 88 | 89 | return true; 90 | }, 91 | nonce 92 | ); 93 | 94 | var N_OVER_TWO = n.shiftRight(1); 95 | 96 | // enforce low S values, see bip62: 'low s values in signatures' 97 | if (s.compareTo(N_OVER_TWO) > 0) { 98 | s = n.subtract(s); 99 | } 100 | 101 | return new ECSignature(r, s); 102 | } 103 | 104 | function verifyRaw(curve, e, signature, Q) { 105 | var n = curve.n; 106 | var G = curve.G; 107 | 108 | var r = signature.r; 109 | var s = signature.s; 110 | 111 | // 1.4.1 Enforce r and s are both integers in the interval [1, n − 1] 112 | if (r.signum() <= 0 || r.compareTo(n) >= 0) return false; 113 | if (s.signum() <= 0 || s.compareTo(n) >= 0) return false; 114 | 115 | // c = s^-1 mod n 116 | var c = s.modInverse(n); 117 | 118 | // 1.4.4 Compute u1 = es^−1 mod n 119 | // u2 = rs^−1 mod n 120 | var u1 = e.multiply(c).mod(n); 121 | var u2 = r.multiply(c).mod(n); 122 | 123 | // 1.4.5 Compute R = (xR, yR) = u1G + u2Q 124 | var R = G.multiplyTwo(u1, Q, u2); 125 | 126 | // 1.4.5 (cont.) Enforce R is not at infinity 127 | if (curve.isInfinity(R)) return false; 128 | 129 | // 1.4.6 Convert the field element R.x to an integer 130 | var xR = R.affineX; 131 | 132 | // 1.4.7 Set v = xR mod n 133 | var v = xR.mod(n); 134 | 135 | // 1.4.8 If v = r, output "valid", and if v != r, output "invalid" 136 | return v.equals(r); 137 | } 138 | 139 | function verify(curve, hash, signature, Q) { 140 | // 1.4.2 H = Hash(M), already done by the user 141 | // 1.4.3 e = H 142 | var e = BigInteger.fromBuffer(hash); 143 | return verifyRaw(curve, e, signature, Q); 144 | } 145 | 146 | /** 147 | * Recover a public key from a signature. 148 | * 149 | * See SEC 1: Elliptic Curve Cryptography, section 4.1.6, "Public 150 | * Key Recovery Operation". 151 | * 152 | * http://www.secg.org/download/aid-780/sec1-v2.pdf 153 | */ 154 | function recoverPubKey(curve, e, signature, i) { 155 | assert.strictEqual(i & 3, i, 'Recovery param is more than two bits'); 156 | 157 | var n = curve.n; 158 | var G = curve.G; 159 | 160 | var r = signature.r; 161 | var s = signature.s; 162 | 163 | assert(r.signum() > 0 && r.compareTo(n) < 0, 'Invalid r value'); 164 | assert(s.signum() > 0 && s.compareTo(n) < 0, 'Invalid s value'); 165 | 166 | // A set LSB signifies that the y-coordinate is odd 167 | var isYOdd = i & 1; 168 | 169 | // The more significant bit specifies whether we should use the 170 | // first or second candidate key. 171 | var isSecondKey = i >> 1; 172 | 173 | // 1.1 Let x = r + jn 174 | var x = isSecondKey ? r.add(n) : r; 175 | var R = curve.pointFromX(isYOdd, x); 176 | 177 | // 1.4 Check that nR is at infinity 178 | var nR = R.multiply(n); 179 | assert(curve.isInfinity(nR), 'nR is not a valid curve point'); 180 | 181 | // Compute -e from e 182 | var eNeg = e.negate().mod(n); 183 | 184 | // 1.6.1 Compute Q = r^-1 (sR - eG) 185 | // Q = r^-1 (sR + -eG) 186 | var rInv = r.modInverse(n); 187 | 188 | var Q = R.multiplyTwo(s, G, eNeg).multiply(rInv); 189 | curve.validate(Q); 190 | 191 | return Q; 192 | } 193 | 194 | /** 195 | * Calculate pubkey extraction parameter. 196 | * 197 | * When extracting a pubkey from a signature, we have to 198 | * distinguish four different cases. Rather than putting this 199 | * burden on the verifier, Bitcoin includes a 2-bit value with the 200 | * signature. 201 | * 202 | * This function simply tries all four cases and returns the value 203 | * that resulted in a successful pubkey recovery. 204 | */ 205 | function calcPubKeyRecoveryParam(curve, e, signature, Q) { 206 | for (var i = 0; i < 4; i++) { 207 | var Qprime = recoverPubKey(curve, e, signature, i); 208 | 209 | // 1.6.2 Verify Q 210 | if (Qprime.equals(Q)) { 211 | return i; 212 | } 213 | } 214 | 215 | throw new Error('Unable to find valid recovery factor'); 216 | } 217 | 218 | export { 219 | calcPubKeyRecoveryParam, 220 | deterministicGenerateK, 221 | recoverPubKey, 222 | sign, 223 | verify, 224 | verifyRaw 225 | }; 226 | -------------------------------------------------------------------------------- /lib/serializer/src/serializer.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import ByteBuffer from 'bytebuffer'; 3 | import EC from './error_with_cause'; 4 | 5 | const HEX_DUMP = process.env.npm_config__graphene_serializer_hex_dump; 6 | 7 | class Serializer { 8 | constructor(operation_name, types) { 9 | this.operation_name = operation_name; 10 | this.types = types || {}; 11 | if (this.types) this.keys = Object.keys(this.types); 12 | 13 | Serializer.printDebug = true; 14 | } 15 | 16 | fromByteBuffer(b) { 17 | var object = {}; 18 | var field = null; 19 | try { 20 | var iterable = this.keys; 21 | for (var i = 0; i < iterable.length; i++) { 22 | field = iterable[i]; 23 | var type = this.types[field]; 24 | try { 25 | if (HEX_DUMP) { 26 | if (type.operation_name) { 27 | console.error(type.operation_name); 28 | } else { 29 | var o1 = b.offset; 30 | type.fromByteBuffer(b); 31 | var o2 = b.offset; 32 | b.offset = o1; 33 | //b.reset() 34 | var _b = b.copy(o1, o2); 35 | console.error(`${this.operation_name}.${field}\t`, _b.toHex()); 36 | } 37 | } 38 | object[field] = type.fromByteBuffer(b); 39 | } catch (e) { 40 | if (Serializer.printDebug) { 41 | console.error( 42 | `Error reading ${this.operation_name}.${field} in data:` 43 | ); 44 | b.printDebug(); 45 | } 46 | throw e; 47 | } 48 | } 49 | } catch (error) { 50 | EC.throw(this.operation_name + '.' + field, error); 51 | } 52 | 53 | return object; 54 | } 55 | 56 | appendByteBuffer(b, object) { 57 | var field = null; 58 | try { 59 | var iterable = this.keys; 60 | for (var i = 0; i < iterable.length; i++) { 61 | field = iterable[i]; 62 | var type = this.types[field]; 63 | type.appendByteBuffer(b, object[field]); 64 | } 65 | } catch (error) { 66 | try { 67 | EC.throw( 68 | this.operation_name + 69 | '.' + 70 | field + 71 | ' = ' + 72 | JSON.stringify(object[field]), 73 | error 74 | ); 75 | } catch (e) { 76 | // circular ref 77 | EC.throw( 78 | this.operation_name + '.' + field + ' = ' + object[field], 79 | error 80 | ); 81 | } 82 | } 83 | return; 84 | } 85 | 86 | fromObject(serialized_object) { 87 | var result = {}; 88 | var field = null; 89 | try { 90 | var iterable = this.keys; 91 | for (var i = 0; i < iterable.length; i++) { 92 | field = iterable[i]; 93 | var type = this.types[field]; 94 | var value = serialized_object[field]; 95 | //DEBUG value = value.resolve if value.resolve 96 | //DEBUG console.log('... value',field,value) 97 | var object = type.fromObject(value); 98 | result[field] = object; 99 | } 100 | } catch (error) { 101 | EC.throw(this.operation_name + '.' + field, error); 102 | } 103 | 104 | return result; 105 | } 106 | 107 | /** 108 | @arg {boolean} [debug.use_default = false] - more template friendly 109 | @arg {boolean} [debug.annotate = false] - add user-friendly information 110 | */ 111 | toObject( 112 | serialized_object = {}, 113 | debug = { use_default: false, annotate: false } 114 | ) { 115 | var result = {}; 116 | var field = null; 117 | try { 118 | if (!this.types) return result; 119 | 120 | var iterable = this.keys; 121 | for (var i = 0; i < iterable.length; i++) { 122 | field = iterable[i]; 123 | var type = this.types[field]; 124 | var object = type.toObject( 125 | typeof serialized_object !== 'undefined' && serialized_object !== null 126 | ? serialized_object[field] 127 | : undefined, 128 | debug 129 | ); 130 | result[field] = object; 131 | if (HEX_DUMP) { 132 | var b = new ByteBuffer( 133 | ByteBuffer.DEFAULT_CAPACITY, 134 | ByteBuffer.LITTLE_ENDIAN 135 | ); 136 | type.appendByteBuffer( 137 | b, 138 | typeof serialized_object !== 'undefined' && 139 | serialized_object !== null 140 | ? serialized_object[field] 141 | : undefined 142 | ); 143 | b = b.copy(0, b.offset); 144 | console.error(this.operation_name + '.' + field, b.toHex()); 145 | } 146 | } 147 | } catch (error) { 148 | EC.throw(this.operation_name + '.' + field, error); 149 | } 150 | 151 | return result; 152 | } 153 | 154 | /** Sort by the first element in a operation */ 155 | compare(a, b) { 156 | let first_key = this.keys[0]; 157 | let first_type = this.types[first_key]; 158 | 159 | let valA = a[first_key]; 160 | let valB = b[first_key]; 161 | 162 | if (first_type.compare) return first_type.compare(valA, valB); 163 | 164 | if (typeof valA === 'number' && typeof valB === 'number') 165 | return valA - valB; 166 | 167 | let encoding; 168 | if (Buffer.isBuffer(valA) && Buffer.isBuffer(valB)) { 169 | // A binary string compare does not work. If localeCompare is well supported that could replace HEX. Performanance is very good so comparing HEX works. 170 | encoding = 'hex'; 171 | } 172 | 173 | let strA = valA.toString(encoding); 174 | let strB = valB.toString(encoding); 175 | return strA > strB ? 1 : strA < strB ? -1 : 0; 176 | } 177 | 178 | // 179 | 180 | fromHex(hex) { 181 | var b = ByteBuffer.fromHex(hex, ByteBuffer.LITTLE_ENDIAN); 182 | return this.fromByteBuffer(b); 183 | } 184 | 185 | fromBuffer(buffer) { 186 | var b = ByteBuffer.fromBinary( 187 | buffer.toString('binary'), 188 | ByteBuffer.LITTLE_ENDIAN 189 | ); 190 | return this.fromByteBuffer(b); 191 | } 192 | 193 | toHex(object) { 194 | // return this.toBuffer(object).toString("hex") 195 | var b = this.toByteBuffer(object); 196 | return b.toHex(); 197 | } 198 | 199 | toByteBuffer(object) { 200 | var b = new ByteBuffer( 201 | ByteBuffer.DEFAULT_CAPACITY, 202 | ByteBuffer.LITTLE_ENDIAN 203 | ); 204 | this.appendByteBuffer(b, object); 205 | return b.copy(0, b.offset); 206 | } 207 | 208 | toBuffer(object) { 209 | return new Buffer(this.toByteBuffer(object).toBinary(), 'binary'); 210 | } 211 | } 212 | 213 | export default Serializer; 214 | -------------------------------------------------------------------------------- /lib/ecc/src/PrivateKey.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { getCurveByName, Point } from 'ecurve'; 3 | import BigInteger from 'bigi'; 4 | import { decode, encode } from 'bs58'; 5 | import { sha256, sha512 } from './hash'; 6 | import PublicKey from './PublicKey'; 7 | import deepEqual from 'deep-equal'; 8 | import assert from 'assert'; 9 | import ByteBuffer from 'bytebuffer'; 10 | 11 | const secp256k1Lib = require('secp256k1'); 12 | 13 | const secp256k1 = getCurveByName('secp256k1'); 14 | // eslint-disable-next-line no-unused-vars 15 | const { G, n } = secp256k1; 16 | 17 | class PrivateKey { 18 | /** 19 | @private see static functions 20 | @param {BigInteger} 21 | */ 22 | constructor(d) { 23 | this.d = d; 24 | } 25 | 26 | static fromBuffer(buf) { 27 | if (!Buffer.isBuffer(buf)) { 28 | throw new Error('Expecting paramter to be a Buffer type'); 29 | } 30 | if (32 !== buf.length) { 31 | console.log( 32 | `WARN: Expecting 32 bytes, instead got ${buf.length}, stack trace:`, 33 | new Error().stack 34 | ); 35 | } 36 | if (buf.length === 0) { 37 | throw new Error('Empty buffer'); 38 | } 39 | return new PrivateKey(BigInteger.fromBuffer(buf)); 40 | } 41 | 42 | /** @arg {string} seed - any length string. This is private, the same seed produces the same private key every time. */ 43 | static fromSeed(seed) { 44 | // generate_private_key 45 | if (!(typeof seed === 'string')) { 46 | throw new Error('seed must be of type string'); 47 | } 48 | return PrivateKey.fromBuffer(sha256(seed)); 49 | } 50 | 51 | /** @return {string} Wallet Import Format (still a secret, Not encrypted) */ 52 | static fromWif(_private_wif) { 53 | var private_wif = new Buffer(decode(_private_wif)); 54 | var version = private_wif.readUInt8(0); 55 | assert.equal( 56 | 0x80, 57 | version, 58 | `Expected version ${0x80}, instead got ${version}` 59 | ); 60 | // checksum includes the version 61 | var private_key = private_wif.slice(0, -4); 62 | var checksum = private_wif.slice(-4); 63 | var new_checksum = sha256(private_key); 64 | new_checksum = sha256(new_checksum); 65 | new_checksum = new_checksum.slice(0, 4); 66 | var isEqual = deepEqual(checksum, new_checksum); //, 'Invalid checksum' 67 | if (!isEqual) { 68 | throw new Error('Checksum did not match'); 69 | } 70 | private_key = private_key.slice(1); 71 | return PrivateKey.fromBuffer(private_key); 72 | } 73 | 74 | toWif() { 75 | var private_key = this.toBuffer(); 76 | // checksum includes the version 77 | private_key = Buffer.concat([new Buffer([0x80]), private_key]); 78 | var checksum = sha256(private_key); 79 | checksum = sha256(checksum); 80 | checksum = checksum.slice(0, 4); 81 | var private_wif = Buffer.concat([private_key, checksum]); 82 | return encode(private_wif); 83 | } 84 | 85 | /** 86 | @return {Point} 87 | */ 88 | toPublicKeyPoint() { 89 | // eslint-disable-next-line no-unused-vars 90 | var Q; 91 | return (Q = secp256k1.G.multiply(this.d)); 92 | } 93 | 94 | toPublicKey() { 95 | if (this.public_key) { 96 | return this.public_key; 97 | } 98 | return (this.public_key = PublicKey.fromPoint(this.toPublicKeyPoint())); 99 | } 100 | 101 | toBuffer() { 102 | return this.d.toBuffer(32); 103 | } 104 | 105 | /** ECIES */ 106 | get_shared_secret(public_key, legacy = false) { 107 | public_key = toPublic(public_key); 108 | let KB = public_key.toUncompressed().toBuffer(); 109 | let KBP = Point.fromAffine( 110 | secp256k1, 111 | 112 | BigInteger.fromBuffer(KB.slice(1, 33)), // x 113 | BigInteger.fromBuffer(KB.slice(33, 65)) // y 114 | ); 115 | let r = this.toBuffer(); 116 | let P = KBP.multiply(BigInteger.fromBuffer(r)); 117 | 118 | let S = P.affineX.toBuffer({ size: 32 }); 119 | 120 | /* 121 | the input to sha512 must be exactly 32-bytes, to match the c++ implementation 122 | of get_shared_secret. Right now S will be shorter if the most significant 123 | byte(s) is zero. Pad it back to the full 32-bytes 124 | */ 125 | if (!legacy && S.length < 32) { 126 | let pad = new Buffer(32 - S.length).fill(0); 127 | S = Buffer.concat([pad, S]); 128 | } 129 | 130 | // SHA512 used in ECIES 131 | return sha512(S); 132 | } 133 | 134 | get_shared_secret_v2(public_key, legacy = false) { 135 | public_key = toPublic(public_key); 136 | let KB = public_key.toUncompressed().toBuffer(); 137 | let shared = PublicKey.fromBuffer( 138 | secp256k1Lib.ecdhUnsafe(KB, this.toBuffer(), legacy) 139 | ); 140 | let S = shared.Q.x.toBuffer({ size: 32 }); 141 | if (!legacy && S.length < 32) { 142 | let pad = new Buffer(32 - S.length).fill(0); 143 | S = Buffer.concat([pad, S]); 144 | } 145 | return sha512(S); 146 | } 147 | 148 | get_shared_secret_v2_no_padding(public_key, legacy = false) { 149 | public_key = toPublic(public_key); 150 | let KB = public_key.toUncompressed().toBuffer(); 151 | let shared = PublicKey.fromBuffer( 152 | secp256k1Lib.ecdhUnsafe(KB, this.toBuffer(), legacy) 153 | ); 154 | return sha512(shared.Q.x.toBuffer()); 155 | } 156 | 157 | // /** ECIES (does not always match the Point.fromAffine version above) */ 158 | // get_shared_secret(public_key){ 159 | // public_key = toPublic(public_key) 160 | // var P = public_key.Q.multiply( this.d ); 161 | // var S = P.affineX.toBuffer({size: 32}); 162 | // // ECIES, adds an extra sha512 163 | // return sha512(S); 164 | // } 165 | 166 | /** @throws {Error} - overflow of the key could not be derived */ 167 | child(offset) { 168 | offset = Buffer.concat([this.toPublicKey().toBuffer(), offset]); 169 | offset = sha256(offset); 170 | let c = BigInteger.fromBuffer(offset); 171 | 172 | if (c.compareTo(n) >= 0) 173 | throw new Error('Child offset went out of bounds, try again'); 174 | 175 | let derived = this.d.add(c); //.mod(n) 176 | 177 | if (derived.signum() === 0) 178 | throw new Error('Child offset derived to an invalid key, try again'); 179 | 180 | return new PrivateKey(derived); 181 | } 182 | 183 | /* */ 184 | 185 | toByteBuffer() { 186 | var b = new ByteBuffer( 187 | ByteBuffer.DEFAULT_CAPACITY, 188 | ByteBuffer.LITTLE_ENDIAN 189 | ); 190 | this.appendByteBuffer(b); 191 | return b.copy(0, b.offset); 192 | } 193 | 194 | static fromHex(hex) { 195 | return PrivateKey.fromBuffer(new Buffer(hex, 'hex')); 196 | } 197 | 198 | toHex() { 199 | return this.toBuffer().toString('hex'); 200 | } 201 | 202 | /* */ 203 | } 204 | 205 | export default PrivateKey; 206 | 207 | let toPublic = (data) => 208 | data == null ? data : data.Q ? data : PublicKey.fromStringOrThrow(data); 209 | -------------------------------------------------------------------------------- /lib/ecc/src/KeyUtils.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import PrivateKey from './PrivateKey'; 3 | import PublicKey from './PublicKey'; 4 | import Address from './address'; 5 | import Aes from './aes'; 6 | 7 | import { sha256, sha512 } from './hash'; 8 | // import dictionary from './dictionary_en'; 9 | import secureRandom from 'secure-random'; 10 | import { ChainConfig } from 'gxbjs-ws'; 11 | 12 | // hash for .25 second 13 | var HASH_POWER_MILLS = 250; 14 | 15 | const key = { 16 | /** Uses 1 second of hashing power to create a key/password checksum. An 17 | implementation can re-call this method with the same password to re-match 18 | the strength of the CPU (either after moving from a desktop to a mobile, 19 | mobile to desktop, or N years from now when CPUs are presumably stronger). 20 | 21 | A salt is used for all the normal reasons... 22 | 23 | @return object { 24 | aes_private: Aes, 25 | checksum: "{hash_iteration_count},{salt},{checksum}" 26 | } 27 | */ 28 | aes_checksum(password) { 29 | if (!(typeof password === 'string')) { 30 | throw new Error('password string required'); 31 | } 32 | var salt = secureRandom.randomBuffer(4).toString('hex'); 33 | var iterations = 0; 34 | var secret = salt + password; 35 | // hash for .1 second 36 | var start_t = Date.now(); 37 | while (Date.now() - start_t < HASH_POWER_MILLS) { 38 | secret = sha256(secret); 39 | iterations += 1; 40 | } 41 | 42 | var checksum = sha256(secret); 43 | var checksum_string = [ 44 | iterations, 45 | salt.toString('hex'), 46 | checksum.slice(0, 4).toString('hex') 47 | ].join(','); 48 | 49 | return { aes_private: Aes.fromSeed(secret), checksum: checksum_string }; 50 | }, 51 | 52 | /** Provide a matching password and key_checksum. A "wrong password" 53 | error is thrown if the password does not match. If this method takes 54 | much more or less than 1 second to return, one should consider updating 55 | all encyrpted fields using a new key.key_checksum. 56 | */ 57 | aes_private(password, key_checksum) { 58 | var [iterations, salt, checksum] = key_checksum.split(','); 59 | var secret = salt + password; 60 | for ( 61 | var i = 0; 62 | 0 < iterations ? i < iterations : i > iterations; 63 | 0 < iterations ? i++ : i++ 64 | ) { 65 | secret = sha256(secret); 66 | } 67 | var new_checksum = sha256(secret); 68 | if (!(new_checksum.slice(0, 4).toString('hex') === checksum)) { 69 | throw new Error('wrong password'); 70 | } 71 | return Aes.fromSeed(secret); 72 | }, 73 | 74 | /** 75 | A week random number generator can run out of entropy. This should ensure even the worst random number implementation will be reasonably safe. 76 | 77 | @param1 string entropy of at least 32 bytes 78 | */ 79 | random32ByteBuffer(entropy = this.browserEntropy()) { 80 | if (!(typeof entropy === 'string')) { 81 | throw new Error('string required for entropy'); 82 | } 83 | 84 | if (entropy.length < 32) { 85 | throw new Error('expecting at least 32 bytes of entropy'); 86 | } 87 | 88 | var start_t = Date.now(); 89 | 90 | while (Date.now() - start_t < HASH_POWER_MILLS) entropy = sha256(entropy); 91 | 92 | var hash_array = []; 93 | hash_array.push(entropy); 94 | 95 | // Hashing for 1 second may helps the computer is not low on entropy (this method may be called back-to-back). 96 | hash_array.push(secureRandom.randomBuffer(32)); 97 | 98 | return sha256(Buffer.concat(hash_array)); 99 | }, 100 | 101 | suggest_brain_key: function( 102 | dictionary = ',', 103 | entropy = this.browserEntropy() 104 | ) { 105 | var randomBuffer = this.random32ByteBuffer(entropy); 106 | 107 | var word_count = 16; 108 | var dictionary_lines = dictionary.split(','); 109 | 110 | if (!(dictionary_lines.length === 49744)) { 111 | throw new Error( 112 | `expecting ${49744} but got ${dictionary_lines.length} dictionary words` 113 | ); 114 | } 115 | 116 | var brainkey = []; 117 | var end = word_count * 2; 118 | 119 | for (let i = 0; i < end; i += 2) { 120 | // randomBuffer has 256 bits / 16 bits per word == 16 words 121 | var num = (randomBuffer[i] << 8) + randomBuffer[i + 1]; 122 | 123 | // convert into a number between 0 and 1 (inclusive) 124 | var rndMultiplier = num / Math.pow(2, 16); 125 | var wordIndex = Math.round(dictionary_lines.length * rndMultiplier); 126 | 127 | brainkey.push(dictionary_lines[wordIndex]); 128 | } 129 | return this.normalize_brainKey(brainkey.join(' ')); 130 | }, 131 | 132 | get_random_key(entropy) { 133 | return PrivateKey.fromBuffer(this.random32ByteBuffer(entropy)); 134 | }, 135 | 136 | get_brainPrivateKey(brainKey, sequence = 0) { 137 | if (sequence < 0) { 138 | throw new Error('invalid sequence'); 139 | } 140 | brainKey = key.normalize_brainKey(brainKey); 141 | return PrivateKey.fromBuffer(sha256(sha512(brainKey + ' ' + sequence))); 142 | }, 143 | 144 | // Turn invisible space like characters into a single space 145 | normalize_brainKey(brainKey) { 146 | if (!(typeof brainKey === 'string')) { 147 | throw new Error('string required for brainKey'); 148 | } 149 | 150 | brainKey = brainKey.trim(); 151 | return brainKey.split(/[\t\n\v\f\r ]+/).join(' '); 152 | }, 153 | 154 | browserEntropy() { 155 | var entropyStr = ''; 156 | try { 157 | entropyStr = 158 | new Date().toString() + 159 | ' ' + 160 | window.screen.height + 161 | ' ' + 162 | window.screen.width + 163 | ' ' + 164 | window.screen.colorDepth + 165 | ' ' + 166 | ' ' + 167 | window.screen.availHeight + 168 | ' ' + 169 | window.screen.availWidth + 170 | ' ' + 171 | window.screen.pixelDepth + 172 | navigator.language + 173 | ' ' + 174 | window.location + 175 | ' ' + 176 | window.history.length; 177 | 178 | for (var i = 0, mimeType; i < navigator.mimeTypes.length; i++) { 179 | mimeType = navigator.mimeTypes[i]; 180 | entropyStr += 181 | mimeType.description + 182 | ' ' + 183 | mimeType.type + 184 | ' ' + 185 | mimeType.suffixes + 186 | ' '; 187 | } 188 | console.log('INFO\tbrowserEntropy gathered'); 189 | } catch (error) { 190 | //nodejs:ReferenceError: window is not defined 191 | entropyStr = sha256(new Date().toString()); 192 | } 193 | 194 | var b = new Buffer(entropyStr); 195 | entropyStr += b.toString('binary') + ' ' + new Date().toString(); 196 | return entropyStr; 197 | }, 198 | 199 | // @return array of 5 legacy addresses for a pubkey string parameter. 200 | addresses(pubkey, address_prefix = ChainConfig.address_prefix) { 201 | var public_key = PublicKey.fromPublicKeyString(pubkey, address_prefix); 202 | // S L O W 203 | var address_string = [ 204 | Address.fromPublic(public_key, false, 0).toString(address_prefix), // btc_uncompressed 205 | Address.fromPublic(public_key, true, 0).toString(address_prefix), // btc_compressed 206 | Address.fromPublic(public_key, false, 56).toString(address_prefix), // pts_uncompressed 207 | Address.fromPublic(public_key, true, 56).toString(address_prefix), // pts_compressed 208 | public_key.toAddressString(address_prefix) // bts_short, most recent format 209 | ]; 210 | return address_string; 211 | } 212 | }; 213 | 214 | export default key; 215 | -------------------------------------------------------------------------------- /test/ecc/Crypto.js: -------------------------------------------------------------------------------- 1 | import { Aes, PrivateKey, PublicKey, Signature, hash, key } from "../../lib"; 2 | import assert from "assert"; 3 | import {Long} from 'bytebuffer'; 4 | 5 | import secureRandom from 'secure-random'; 6 | 7 | import dictionary from "./dictionary"; 8 | 9 | describe("ECC", function() { 10 | 11 | describe("Crypto", function() { 12 | 13 | var encrypted_key = 14 | "37fd6a251d262ec4c25343016a024a3aec543b7a43a208bf66bc80640dff" + 15 | "8ac8d52ae4ad7500d067c90f26189f9ee6050a13c087d430d24b88e713f1" + 16 | "5d32cbd59e61b0e69c75da93f43aabb11039d06f"; 17 | 18 | var decrypted_key = 19 | "ab0cb9a14ecaa3078bfee11ca0420ea2" + 20 | "3f5d49d7a7c97f7f45c3a520106491f8" + // 64 hex digits 21 | "00000000000000000000000000000000000000000000000000000000" + 22 | "00000000"; 23 | 24 | it("Decrypt", function() { 25 | var aes = Aes.fromSeed("Password01") 26 | var d = aes.decryptHex(encrypted_key) 27 | assert.equal(decrypted_key, d, "decrypted key does not match") 28 | }) 29 | 30 | it("Encrypt", function() { 31 | var aes = Aes.fromSeed("Password01") 32 | var d = aes.encryptHex(decrypted_key) 33 | assert.equal(encrypted_key, d, "encrypted key does not match") 34 | }) 35 | 36 | /*it "Computes public key", -> 37 | private_key = PrivateKey.fromHex decrypted_key.substring 0, 64 38 | public_key = private_key.toPublicKey() 39 | console.log public_key.toHex());*/ 40 | 41 | it("generate private key from seed", function() { 42 | var private_key = PrivateKey.fromSeed("1"); 43 | assert.equal(private_key.toPublicKey().toString(), "GPH8m5UgaFAAYQRuaNejYdS8FVLVp9Ss3K1qAVk5de6F8s3HnVbvA", "private key does not match"); 44 | }) 45 | 46 | it("sign", function() { 47 | this.timeout(10000); 48 | var private_key = PrivateKey.fromSeed("1"); 49 | return (() => { 50 | var result = []; 51 | for (var i = 0; i < 10; i++) { 52 | result.push(Signature.signBuffer((new Buffer(i)), private_key)); 53 | } 54 | return result; 55 | })(); 56 | }); 57 | 58 | it("binary_encryption", function() { 59 | var sender = PrivateKey.fromSeed("1"); 60 | var receiver = PrivateKey.fromSeed("2"); 61 | var S = sender.get_shared_secret(receiver.toPublicKey()); 62 | var nonce = "289662526069530675"; 63 | 64 | var ciphertext = Aes.encrypt_with_checksum( 65 | sender, 66 | receiver.toPublicKey(), 67 | nonce, 68 | new Buffer("\xff\x00", 'binary') 69 | ); 70 | //console.log '... ciphertext',ciphertext 71 | var plaintext = Aes.decrypt_with_checksum( 72 | receiver, 73 | sender.toPublicKey(), 74 | nonce, 75 | ciphertext 76 | ); 77 | //console.log '... plaintext',plaintext.toString() 78 | assert.equal("ff00", plaintext.toString('hex')); 79 | }); 80 | 81 | // time-based, probably want to keep these last 82 | it("key_checksum", function(){ 83 | this.timeout(1500); 84 | return min_time_elapsed(function(){ 85 | var key_checksum = key.aes_checksum("password").checksum; 86 | assert.equal( 87 | true, 88 | key_checksum.length > 4+4+2, 89 | "key_checksum too short" 90 | ); 91 | assert.equal(3, key_checksum.split(',').length); 92 | }); 93 | }); 94 | 95 | it("key_checksum with aes_private", function(done){ 96 | this.timeout(1500); 97 | return min_time_elapsed(function(){ 98 | var aes_checksum = key.aes_checksum("password"); 99 | var aes_private = aes_checksum.aes_private; 100 | var key_checksum = aes_checksum.checksum; 101 | assert(aes_private !== null); 102 | assert(typeof aes_private["decrypt"] === 'function'); 103 | assert.equal( 104 | true, 105 | key_checksum.length > 4+4+2, 106 | "key_checksum too short" 107 | ); 108 | assert.equal(3, key_checksum.split(',').length); 109 | return done(); 110 | }); 111 | }); 112 | // DEBUG console.log('... key_checksum',key_checksum) 113 | 114 | it("wrong password", function() { 115 | this.timeout(2500); 116 | var key_checksum = min_time_elapsed(function(){ 117 | return key.aes_checksum("password").checksum; 118 | }); 119 | // DEBUG console.log('... key_checksum',key_checksum) 120 | assert.throws(()=> 121 | min_time_elapsed(function(){ 122 | key.aes_private("bad password", key_checksum); 123 | }) 124 | , "wrong password") 125 | }); 126 | 127 | it("password aes_private", function() { 128 | this.timeout(2500); 129 | var key_checksum = min_time_elapsed(function(){ 130 | return key.aes_checksum("password").checksum; 131 | }); 132 | 133 | var password_aes = min_time_elapsed(function(){ 134 | return key.aes_private("password", key_checksum); 135 | }); 136 | 137 | // DEBUG console.log('... password_aes',password_aes) 138 | assert(password_aes !== null); 139 | }); 140 | 141 | }) 142 | 143 | describe("Derivation", ()=> { 144 | 145 | let one_time_private = PrivateKey.fromHex("8fdfdde486f696fd7c6313325e14d3ff0c34b6e2c390d1944cbfe150f4457168") 146 | let to_public = PublicKey.fromStringOrThrow("GPH7vbxtK1WaZqXsiCHPcjVFBewVj8HFRd5Z5XZDpN6Pvb2dZcMqK") 147 | let secret = one_time_private.get_shared_secret( to_public ) 148 | let child = hash.sha256( secret ) 149 | 150 | // Check everything above with `wdump((child));` from the witness_node: 151 | assert.equal(child.toString('hex'), "1f296fa48172d9af63ef3fb6da8e369e6cc33c1fb7c164207a3549b39e8ef698") 152 | 153 | let nonce = hash.sha256( one_time_private.toBuffer() ) 154 | assert.equal(nonce.toString('hex'), "462f6c19ece033b5a3dba09f1e1d7935a5302e4d1eac0a84489cdc8339233fbf") 155 | 156 | it("child from public", function() { 157 | assert.equal( 158 | to_public.child(child).toString(), 159 | "GPH6XA72XARQCain961PCJnXiKYdEMrndNGago2PV5bcUiVyzJ6iL", 160 | "derive child public key" 161 | ); 162 | }); 163 | 164 | // child = sha256( one_time_private.get_secret( to_public )) 165 | it("child from private", function() { 166 | assert.equal( 167 | PrivateKey.fromSeed("alice-brain-key").child(child).toPublicKey().toString(), 168 | "GPH6XA72XARQCain961PCJnXiKYdEMrndNGago2PV5bcUiVyzJ6iL", 169 | "derive child from private key" 170 | ) 171 | }) 172 | 173 | it("Suggest brainkey", function() { 174 | let brainKey = key.suggest_brain_key(dictionary.en); 175 | assert.equal(16, brainKey.split(" ").length); 176 | }) 177 | 178 | 179 | 180 | // "many keys" works, not really needed 181 | // it("many keys", function() { 182 | // 183 | // this.timeout(10 * 1000) 184 | // 185 | // for (var i = 0; i < 10; i++) { 186 | // let privkey1 = key.get_random_key() 187 | // let privkey2 = key.get_random_key() 188 | // 189 | // let secret1 = one_time_private.get_shared_secret( privkey1.toPublicKey() ) 190 | // let child1 = sha256( secret1 ) 191 | // 192 | // let secret2 = privkey2.get_shared_secret( privkey2.toPublicKey() ) 193 | // let child2 = sha256( secret2 ) 194 | // 195 | // it("child from public", ()=> assert.equal( 196 | // privkey1.toPublicKey().child(child1).toString(), 197 | // privkey2.toPublicKey().child(child2).toString(), 198 | // "derive child public key" 199 | // )) 200 | // 201 | // it("child from private", ()=> assert.equal( 202 | // privkey1.child(child1).toString(), 203 | // privkey2.child(child2).toString(), 204 | // "derive child private key" 205 | // )) 206 | // } 207 | // 208 | // }) 209 | 210 | }) 211 | }); 212 | 213 | var min_time_elapsed = function(f){ 214 | var start_t = Date.now(); 215 | var ret = f(); 216 | var elapsed = Date.now() - start_t; 217 | assert.equal( 218 | // repeat operations may take less time 219 | elapsed >= 250 * 0.8, true, 220 | `minimum time requirement was not met, instead only ${elapsed/1000.0} elapsed` 221 | ); 222 | return ret; 223 | }; 224 | -------------------------------------------------------------------------------- /lib/serializer/src/SerializerValidation.js: -------------------------------------------------------------------------------- 1 | import { Long } from 'bytebuffer'; 2 | import ChainTypes from '../../chain/src/ChainTypes'; 3 | 4 | var MAX_SAFE_INT = 9007199254740991; 5 | var MIN_SAFE_INT = -9007199254740991; 6 | 7 | /** 8 | Most validations are skipped and the value returned unchanged when an empty string, null, or undefined is encountered (except "required"). 9 | 10 | Validations support a string format for dealing with large numbers. 11 | */ 12 | var _my = { 13 | is_empty: function(value) { 14 | return value === null || value === undefined; 15 | }, 16 | 17 | required(value, field_name = '') { 18 | if (this.is_empty(value)) { 19 | throw new Error(`value required ${field_name} ${value}`); 20 | } 21 | return value; 22 | }, 23 | 24 | require_long(value, field_name = '') { 25 | if (!Long.isLong(value)) { 26 | throw new Error(`Long value required ${field_name} ${value}`); 27 | } 28 | return value; 29 | }, 30 | 31 | string(value) { 32 | if (this.is_empty(value)) { 33 | return value; 34 | } 35 | if (typeof value !== 'string') { 36 | throw new Error(`string required: ${value}`); 37 | } 38 | return value; 39 | }, 40 | 41 | number(value) { 42 | if (this.is_empty(value)) { 43 | return value; 44 | } 45 | if (typeof value !== 'number') { 46 | throw new Error(`number required: ${value}`); 47 | } 48 | return value; 49 | }, 50 | 51 | whole_number(value, field_name = '') { 52 | if (this.is_empty(value)) { 53 | return value; 54 | } 55 | if (/\./.test(value)) { 56 | throw new Error(`whole number required ${field_name} ${value}`); 57 | } 58 | return value; 59 | }, 60 | 61 | unsigned(value, field_name = '') { 62 | if (this.is_empty(value)) { 63 | return value; 64 | } 65 | if (/-/.test(value)) { 66 | throw new Error(`unsigned required ${field_name} ${value}`); 67 | } 68 | return value; 69 | }, 70 | 71 | is_digits: function(value) { 72 | if (typeof value === 'number') { 73 | return true; 74 | } 75 | return /^[0-9]+$/.test(value); 76 | }, 77 | 78 | to_number: function(value, field_name = '') { 79 | if (this.is_empty(value)) { 80 | return value; 81 | } 82 | this.no_overflow53(value, field_name); 83 | var int_value = (() => { 84 | if (typeof value === 'number') { 85 | return value; 86 | } else { 87 | return parseInt(value); 88 | } 89 | })(); 90 | return int_value; 91 | }, 92 | 93 | to_long(value, field_name = '', unsigned = false) { 94 | if (this.is_empty(value)) { 95 | return value; 96 | } 97 | if (Long.isLong(value)) { 98 | return value; 99 | } 100 | 101 | this.no_overflow64(value, field_name, unsigned); 102 | if (typeof value === 'number') { 103 | value = '' + value; 104 | } 105 | return Long.fromString(value, unsigned); 106 | }, 107 | 108 | to_string(value, field_name = '') { 109 | if (this.is_empty(value)) { 110 | return value; 111 | } 112 | if (typeof value === 'string') { 113 | return value; 114 | } 115 | if (typeof value === 'number') { 116 | this.no_overflow53(value, field_name); 117 | return '' + value; 118 | } 119 | if (Long.isLong(value)) { 120 | return value.toString(); 121 | } 122 | throw `unsupported type ${field_name}: (${typeof value}) ${value}`; 123 | }, 124 | 125 | require_test(regex, value, field_name = '') { 126 | if (this.is_empty(value)) { 127 | return value; 128 | } 129 | if (!regex.test(value)) { 130 | throw new Error(`unmatched ${regex} ${field_name} ${value}`); 131 | } 132 | return value; 133 | }, 134 | 135 | require_match: function(regex, value, field_name = '') { 136 | if (this.is_empty(value)) { 137 | return value; 138 | } 139 | var match = value.match(regex); 140 | if (match === null) { 141 | throw new Error(`unmatched ${regex} ${field_name} ${value}`); 142 | } 143 | return match; 144 | }, 145 | 146 | require_object_id: function(value, field_name) { 147 | return this.require_match( 148 | /^([0-9]+)\.([0-9]+)\.([0-9]+)$/, 149 | value, 150 | field_name 151 | ); 152 | }, 153 | 154 | // Does not support over 53 bits 155 | require_range(min, max, value, field_name = '') { 156 | if (this.is_empty(value)) { 157 | return value; 158 | } 159 | this.to_number(value); 160 | if (value < min || value > max) { 161 | throw new Error(`out of range ${value} ${field_name} ${value}`); 162 | } 163 | return value; 164 | }, 165 | 166 | require_object_type: function( 167 | reserved_spaces = 1, 168 | type, 169 | value, 170 | field_name = '' 171 | ) { 172 | if (this.is_empty(value)) { 173 | return value; 174 | } 175 | var object_type = ChainTypes.object_type[type]; 176 | if (!object_type) { 177 | throw new Error(`Unknown object type ${type} ${field_name} ${value}`); 178 | } 179 | var re = new RegExp(`${reserved_spaces}.${object_type}.[0-9]+$`); 180 | if (!re.test(value)) { 181 | throw new Error( 182 | `Expecting ${type} in format ` + 183 | `${reserved_spaces}.${object_type}.[0-9]+ ` + 184 | `instead of ${value} ${field_name} ${value}` 185 | ); 186 | } 187 | return value; 188 | }, 189 | 190 | get_instance: function(reserve_spaces, type, value, field_name) { 191 | if (this.is_empty(value)) { 192 | return value; 193 | } 194 | this.require_object_type(reserve_spaces, type, value, field_name); 195 | return this.to_number(value.split('.')[2]); 196 | }, 197 | 198 | require_relative_type: function(type, value, field_name) { 199 | this.require_object_type(0, type, value, field_name); 200 | return value; 201 | }, 202 | 203 | get_relative_instance: function(type, value, field_name) { 204 | if (this.is_empty(value)) { 205 | return value; 206 | } 207 | this.require_object_type(0, type, value, field_name); 208 | return this.to_number(value.split('.')[2]); 209 | }, 210 | 211 | require_protocol_type: function(type, value, field_name) { 212 | this.require_object_type(1, type, value, field_name); 213 | return value; 214 | }, 215 | 216 | get_protocol_instance: function(type, value, field_name) { 217 | if (this.is_empty(value)) { 218 | return value; 219 | } 220 | this.require_object_type(1, type, value, field_name); 221 | return this.to_number(value.split('.')[2]); 222 | }, 223 | 224 | get_protocol_type: function(value, field_name) { 225 | if (this.is_empty(value)) { 226 | return value; 227 | } 228 | this.require_object_id(value, field_name); 229 | var values = value.split('.'); 230 | return this.to_number(values[1]); 231 | }, 232 | 233 | get_protocol_type_name(value, field_name) { 234 | if (this.is_empty(value)) { 235 | return value; 236 | } 237 | var type_id = this.get_protocol_type(value, field_name); 238 | return Object.keys(ChainTypes.object_type)[type_id]; 239 | }, 240 | 241 | require_implementation_type: function(type, value, field_name) { 242 | this.require_object_type(2, type, value, field_name); 243 | return value; 244 | }, 245 | 246 | get_implementation_instance: function(type, value, field_name) { 247 | if (this.is_empty(value)) { 248 | return value; 249 | } 250 | this.require_object_type(2, type, value, field_name); 251 | return this.to_number(value.split('.')[2]); 252 | }, 253 | 254 | // signed / unsigned decimal 255 | no_overflow53(value, field_name = '') { 256 | if (typeof value === 'number') { 257 | if (value > MAX_SAFE_INT || value < MIN_SAFE_INT) { 258 | throw new Error(`overflow ${field_name} ${value}`); 259 | } 260 | return; 261 | } 262 | if (typeof value === 'string') { 263 | parseInt(value); 264 | if (value > MAX_SAFE_INT || value < MIN_SAFE_INT) { 265 | throw new Error(`overflow ${field_name} ${value}`); 266 | } 267 | return; 268 | } 269 | if (Long.isLong(value)) { 270 | // typeof value.toInt() is 'number' 271 | this.no_overflow53(value.toInt(), field_name); 272 | return; 273 | } 274 | throw `unsupported type ${field_name}: (${typeof value}) ${value}`; 275 | }, 276 | 277 | // signed / unsigned whole numbers only 278 | no_overflow64(value, field_name = '', unsigned) { 279 | // https://github.com/dcodeIO/Long.js/issues/20 280 | if (Long.isLong(value)) { 281 | return; 282 | } 283 | 284 | // BigInteger#isBigInteger https://github.com/cryptocoinjs/bigi/issues/20 285 | if (value.t !== undefined && value.s !== undefined) { 286 | this.no_overflow64(value.toString(), field_name); 287 | return; 288 | } 289 | 290 | if (typeof value === 'string') { 291 | // remove leading zeros, will cause a false positive 292 | value = value.replace(/^0+/, ''); 293 | // remove trailing zeros 294 | while (/0$/.test(value)) { 295 | value = value.substring(0, value.length - 1); 296 | } 297 | if (/\.$/.test(value)) { 298 | // remove trailing dot 299 | value = value.substring(0, value.length - 1); 300 | } 301 | if (value === '') { 302 | value = '0'; 303 | } 304 | var long_string = Long.fromString(value, unsigned).toString(); 305 | if (long_string !== value.trim()) { 306 | throw new Error(`overflow ${field_name} ${value}`); 307 | } 308 | return; 309 | } 310 | if (typeof value === 'number') { 311 | if (value > MAX_SAFE_INT || value < MIN_SAFE_INT) { 312 | throw new Error(`overflow ${field_name} ${value}`); 313 | } 314 | return; 315 | } 316 | 317 | throw `unsupported type ${field_name}: (${typeof value}) ${value}`; 318 | } 319 | }; 320 | 321 | export default _my; 322 | -------------------------------------------------------------------------------- /lib/ecc/src/aes.js: -------------------------------------------------------------------------------- 1 | // https://code.google.com/p/crypto-js 2 | import AES from 'crypto-js/aes'; 3 | import encHex from 'crypto-js/enc-hex'; 4 | import encBase64 from 'crypto-js/enc-base64'; 5 | import assert from 'assert'; 6 | import { sha256, sha512 } from './hash'; 7 | 8 | /** Provides symetric encrypt and decrypt via AES. */ 9 | class Aes { 10 | /** @private */ 11 | constructor(iv, key) { 12 | (this.iv = iv), (this.key = key); 13 | } 14 | 15 | /** This is an excellent way to ensure that all references to Aes can not operate anymore (example: a wallet becomes locked). An application should ensure there is only one Aes object instance for a given secret `seed`. */ 16 | clear() { 17 | return (this.iv = this.key = undefined); 18 | } 19 | 20 | /** @arg {string} seed - secret seed may be used to encrypt or decrypt. */ 21 | static fromSeed(seed) { 22 | if (seed === undefined) { 23 | throw new Error('seed is required'); 24 | } 25 | var _hash = sha512(seed); 26 | _hash = _hash.toString('hex'); 27 | // DEBUG console.log('... fromSeed _hash',_hash) 28 | return Aes.fromSha512(_hash); 29 | } 30 | 31 | /** @arg {string} hash - A 128 byte hex string, typically one would call {@link fromSeed} instead. */ 32 | static fromSha512(hash) { 33 | assert.equal( 34 | hash.length, 35 | 128, 36 | `A Sha512 in HEX should be 128 characters long, instead got ${hash.length}` 37 | ); 38 | var iv = encHex.parse(hash.substring(64, 96)); 39 | var key = encHex.parse(hash.substring(0, 64)); 40 | return new Aes(iv, key); 41 | } 42 | 43 | static fromBuffer(buf) { 44 | assert(Buffer.isBuffer(buf), 'Expecting Buffer'); 45 | assert.equal( 46 | buf.length, 47 | 64, 48 | `A Sha512 Buffer should be 64 characters long, instead got ${buf.length}` 49 | ); 50 | return Aes.fromSha512(buf.toString('hex')); 51 | } 52 | /** 53 | @throws {Error} - "Invalid Key, ..." 54 | @arg {PrivateKey} private_key - required and used for decryption 55 | @arg {PublicKey} public_key - required and used to calcualte the shared secret 56 | @arg {string} [nonce = ""] optional but should always be provided and be unique when re-using the same private/public keys more than once. This nonce is not a secret. 57 | @arg {string|Buffer} message - Encrypted message containing a checksum 58 | @return {Buffer} 59 | */ 60 | static decrypt_with_checksum( 61 | private_key, 62 | public_key, 63 | nonce, 64 | message, 65 | legacy = false 66 | ) { 67 | // Warning: Do not put `nonce = ""` in the arguments, in es6 this will not convert "null" into an emtpy string 68 | if (nonce == null) 69 | // null or undefined 70 | nonce = ''; 71 | 72 | if (!Buffer.isBuffer(message)) { 73 | message = new Buffer(message, 'hex'); 74 | } 75 | 76 | var S = private_key.get_shared_secret_v2(public_key, legacy); 77 | var planebuffer; 78 | var aes; 79 | try { 80 | aes = Aes.fromSeed( 81 | Buffer.concat([ 82 | // A null or empty string nonce will not effect the hash 83 | new Buffer('' + nonce), 84 | new Buffer(S.toString('hex')) 85 | ]) 86 | ); 87 | 88 | planebuffer = aes.decrypt(message); 89 | } catch (ex) { 90 | // fallback with a shared secret with no padding 91 | S = private_key.get_shared_secret_v2_no_padding(public_key, legacy); 92 | aes = Aes.fromSeed( 93 | Buffer.concat([ 94 | // A null or empty string nonce will not effect the hash 95 | new Buffer('' + nonce), 96 | new Buffer(S.toString('hex')) 97 | ]) 98 | ); 99 | 100 | planebuffer = aes.decrypt(message); 101 | } 102 | 103 | if (!(planebuffer.length >= 4)) { 104 | throw new Error('Invalid key, could not decrypt message(1)'); 105 | } 106 | 107 | // DEBUG console.log('... planebuffer',planebuffer) 108 | var checksum = planebuffer.slice(0, 4); 109 | var plaintext = planebuffer.slice(4); 110 | 111 | // console.log('... checksum',checksum.toString('hex')) 112 | // console.log('... plaintext',plaintext.toString()) 113 | 114 | var new_checksum = sha256(plaintext); 115 | new_checksum = new_checksum.slice(0, 4); 116 | new_checksum = new_checksum.toString('hex'); 117 | 118 | if (!(checksum.toString('hex') === new_checksum)) { 119 | throw new Error('Invalid key, could not decrypt message(2)'); 120 | } 121 | 122 | return plaintext; 123 | } 124 | 125 | /** Identical to {@link decrypt_with_checksum} but used to encrypt. Should not throw an error. 126 | @return {Buffer} message - Encrypted message which includes a checksum 127 | */ 128 | static encrypt_with_checksum(private_key, public_key, nonce, message) { 129 | // Warning: Do not put `nonce = ""` in the arguments, in es6 this will not convert "null" into an emtpy string 130 | 131 | if (nonce == null) 132 | // null or undefined 133 | nonce = ''; 134 | 135 | if (!Buffer.isBuffer(message)) { 136 | message = new Buffer(message, 'binary'); 137 | } 138 | 139 | var S = private_key.get_shared_secret_v2(public_key); 140 | 141 | // D E B U G 142 | // console.log('encrypt_with_checksum', { 143 | // priv_to_pub: private_key.toPublicKey().toString() 144 | // pub: public_key.toPublicKeyString() 145 | // nonce: nonce 146 | // message: message.length 147 | // S: S.toString('hex') 148 | // }) 149 | 150 | var aes = Aes.fromSeed( 151 | Buffer.concat([ 152 | // A null or empty string nonce will not effect the hash 153 | new Buffer('' + nonce), 154 | new Buffer(S.toString('hex')) 155 | ]) 156 | ); 157 | // DEBUG console.log('... S',S.toString('hex')) 158 | var checksum = sha256(message).slice(0, 4); 159 | var payload = Buffer.concat([checksum, message]); 160 | // DEBUG console.log('... payload',payload.toString()) 161 | return aes.encrypt(payload); 162 | } 163 | 164 | /** @private */ 165 | _decrypt_word_array(cipher) { 166 | // https://code.google.com/p/crypto-js/#Custom_Key_and_IV 167 | // see wallet_records.cpp master_key::decrypt_key 168 | return AES.decrypt({ ciphertext: cipher, salt: null }, this.key, { 169 | iv: this.iv 170 | }); 171 | } 172 | 173 | /** @private */ 174 | _encrypt_word_array(plaintext) { 175 | //https://code.google.com/p/crypto-js/issues/detail?id=85 176 | var cipher = AES.encrypt(plaintext, this.key, { iv: this.iv }); 177 | return encBase64.parse(cipher.toString()); 178 | } 179 | 180 | /** This method does not use a checksum, the returned data must be validated some other way. 181 | @arg {string} ciphertext 182 | @return {Buffer} binary 183 | */ 184 | decrypt(ciphertext) { 185 | if (typeof ciphertext === 'string') { 186 | ciphertext = new Buffer(ciphertext, 'binary'); 187 | } 188 | if (!Buffer.isBuffer(ciphertext)) { 189 | throw new Error('buffer required'); 190 | } 191 | assert(ciphertext, 'Missing cipher text'); 192 | // hex is the only common format 193 | var hex = this.decryptHex(ciphertext.toString('hex')); 194 | return new Buffer(hex, 'hex'); 195 | } 196 | 197 | /** This method does not use a checksum, the returned data must be validated some other way. 198 | @arg {string} plaintext 199 | @return {Buffer} binary 200 | */ 201 | encrypt(plaintext) { 202 | if (typeof plaintext === 'string') { 203 | plaintext = new Buffer(plaintext, 'binary'); 204 | } 205 | if (!Buffer.isBuffer(plaintext)) { 206 | throw new Error('buffer required'); 207 | } 208 | //assert plaintext, "Missing plain text" 209 | // hex is the only common format 210 | var hex = this.encryptHex(plaintext.toString('hex')); 211 | return new Buffer(hex, 'hex'); 212 | } 213 | 214 | /** This method does not use a checksum, the returned data must be validated some other way. 215 | @arg {string|Buffer} plaintext 216 | @return {string} hex 217 | */ 218 | encryptToHex(plaintext) { 219 | if (typeof plaintext === 'string') { 220 | plaintext = new Buffer(plaintext, 'binary'); 221 | } 222 | if (!Buffer.isBuffer(plaintext)) { 223 | throw new Error('buffer required'); 224 | } 225 | //assert plaintext, "Missing plain text" 226 | // hex is the only common format 227 | return this.encryptHex(plaintext.toString('hex')); 228 | } 229 | 230 | /** This method does not use a checksum, the returned data must be validated some other way. 231 | @arg {string} cipher - hex 232 | @return {string} binary (could easily be readable text) 233 | */ 234 | decryptHex(cipher) { 235 | assert(cipher, 'Missing cipher text'); 236 | // Convert data into word arrays (used by Crypto) 237 | var cipher_array = encHex.parse(cipher); 238 | var plainwords = this._decrypt_word_array(cipher_array); 239 | return encHex.stringify(plainwords); 240 | } 241 | 242 | /** This method does not use a checksum, the returned data must be validated some other way. 243 | @arg {string} cipher - hex 244 | @return {Buffer} encoded as specified by the parameter 245 | */ 246 | decryptHexToBuffer(cipher) { 247 | assert(cipher, 'Missing cipher text'); 248 | // Convert data into word arrays (used by Crypto) 249 | var cipher_array = encHex.parse(cipher); 250 | var plainwords = this._decrypt_word_array(cipher_array); 251 | var plainhex = encHex.stringify(plainwords); 252 | return new Buffer(plainhex, 'hex'); 253 | } 254 | 255 | /** This method does not use a checksum, the returned data must be validated some other way. 256 | @arg {string} cipher - hex 257 | @arg {string} [encoding = 'binary'] - a valid Buffer encoding 258 | @return {String} encoded as specified by the parameter 259 | */ 260 | decryptHexToText(cipher, encoding = 'binary') { 261 | return this.decryptHexToBuffer(cipher).toString(encoding); 262 | } 263 | 264 | /** This method does not use a checksum, the returned data must be validated some other way. 265 | @arg {string} plainhex - hex format 266 | @return {String} hex 267 | */ 268 | encryptHex(plainhex) { 269 | var plain_array = encHex.parse(plainhex); 270 | var cipher_array = this._encrypt_word_array(plain_array); 271 | return encHex.stringify(cipher_array); 272 | } 273 | } 274 | 275 | export default Aes; 276 | -------------------------------------------------------------------------------- /lib/chain/src/TransactionBuilder.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import { Signature, PublicKey, hash } from "../../ecc"; 3 | import { ops } from "../../serializer"; 4 | import { Apis, ChainConfig } from "gxbjs-ws"; 5 | 6 | import ChainTypes from "./ChainTypes"; 7 | var head_block_time_string, committee_min_review; 8 | 9 | class TransactionBuilder { 10 | 11 | constructor(signProvider = null) { 12 | if(!!signProvider){ 13 | // a function,first param is transaction instance,second is chain_id, must return array buffer like [buffer,buffer] 14 | this.signProvider = signProvider; 15 | } 16 | this.ref_block_num = 0; 17 | this.ref_block_prefix = 0; 18 | this.expiration = 0; 19 | this.operations = []; 20 | this.signatures = []; 21 | this.signer_private_keys = []; 22 | 23 | // semi-private method bindings 24 | this._broadcast = _broadcast.bind(this); 25 | } 26 | 27 | /** 28 | @arg {string} name - like "transfer" 29 | @arg {object} operation - JSON matchching the operation's format 30 | */ 31 | add_type_operation(name, operation) { 32 | this.add_operation(this.get_type_operation(name, operation)); 33 | return; 34 | } 35 | 36 | /** 37 | This does it all: set fees, finalize, sign, and broadcast (if wanted). 38 | 39 | @arg {ConfidentialWallet} cwallet - must be unlocked, used to gather signing keys 40 | 41 | @arg {array} [signer_pubkeys = null] - Optional ["GPHAbc9Def0...", ...]. These are additional signing keys. Some balance claims require propritary address formats, the witness node can't tell us which ones are needed so they must be passed in. If the witness node can figure out a signing key (mostly all other transactions), it should not be passed in here. 42 | 43 | @arg {boolean} [broadcast = false] 44 | */ 45 | process_transaction(cwallet, signer_pubkeys = null, broadcast = false) { 46 | 47 | let wallet_object = cwallet.wallet.wallet_object; 48 | if (Apis.instance().chain_id !== wallet_object.get("chain_id")) 49 | return Promise.reject("Mismatched chain_id; expecting " + 50 | wallet_object.get("chain_id") + ", but got " + 51 | Apis.instance().chain_id); 52 | 53 | return this.set_required_fees().then(() => { 54 | var signer_pubkeys_added = {}; 55 | if (signer_pubkeys) { 56 | 57 | // Balance claims are by address, only the private 58 | // key holder can know about these additional 59 | // potential keys. 60 | var pubkeys = cwallet.getPubkeys_having_PrivateKey(signer_pubkeys); 61 | if (!pubkeys.length) 62 | throw new Error("Missing signing key"); 63 | 64 | for (let pubkey_string of pubkeys) { 65 | var private_key = cwallet.getPrivateKey(pubkey_string); 66 | this.add_signer(private_key, pubkey_string); 67 | signer_pubkeys_added[pubkey_string] = true; 68 | } 69 | } 70 | 71 | return this.get_potential_signatures().then(({ pubkeys, addys }) => { 72 | var my_pubkeys = cwallet.getPubkeys_having_PrivateKey(pubkeys, addys); 73 | 74 | //{//Testing only, don't send All public keys! 75 | // var pubkeys_all = PrivateKeyStore.getPubkeys() // All public keys 76 | // this.get_required_signatures(pubkeys_all).then( required_pubkey_strings => 77 | // console.log('get_required_signatures all\t',required_pubkey_strings.sort(), pubkeys_all)) 78 | // this.get_required_signatures(my_pubkeys).then( required_pubkey_strings => 79 | // console.log('get_required_signatures normal\t',required_pubkey_strings.sort(), pubkeys)) 80 | //} 81 | 82 | return this.get_required_signatures(my_pubkeys).then(required_pubkeys => { 83 | for (let pubkey_string of required_pubkeys) { 84 | if (signer_pubkeys_added[pubkey_string]) continue; 85 | var private_key = cwallet.getPrivateKey(pubkey_string); 86 | if (!private_key) 87 | // This should not happen, get_required_signatures will only 88 | // returned keys from my_pubkeys 89 | throw new Error("Missing signing key for " + pubkey_string); 90 | this.add_signer(private_key, pubkey_string); 91 | } 92 | }); 93 | }) 94 | .then(() => broadcast ? this.broadcast() : this.serialize()); 95 | }); 96 | } 97 | 98 | /** Typically this is called automatically just prior to signing. Once finalized this transaction can not be changed. */ 99 | finalize() { 100 | return new Promise((resolve, reject) => { 101 | 102 | if (this.tr_buffer) { throw new Error("already finalized"); } 103 | 104 | resolve(Apis.instance().db_api().exec("get_objects", [["2.1.0"]]).then((r) => { 105 | head_block_time_string = r[0].time; 106 | if (this.expiration === 0) 107 | this.expiration = base_expiration_sec() + ChainConfig.expire_in_secs; 108 | this.ref_block_num = r[0].head_block_number & 0xFFFF; 109 | this.ref_block_prefix = new Buffer(r[0].head_block_id, "hex").readUInt32LE(4); 110 | //DEBUG console.log("ref_block",@ref_block_num,@ref_block_prefix,r) 111 | 112 | var iterable = this.operations; 113 | for (var i = 0, op; i < iterable.length; i++) { 114 | op = iterable[i]; 115 | if (op[1]["finalize"]) { 116 | op[1].finalize(); 117 | } 118 | } 119 | this.tr_buffer = ops.transaction.toBuffer(this); 120 | })); 121 | 122 | }); 123 | } 124 | 125 | /** @return {string} hex transaction ID */ 126 | id() { 127 | if (!this.tr_buffer) { throw new Error("not finalized"); } 128 | return hash.sha256(this.tr_buffer).toString("hex").substring(0, 40); 129 | } 130 | 131 | /** 132 | Typically one will use {@link this.add_type_operation} instead. 133 | @arg {array} operation - [operation_id, operation] 134 | */ 135 | add_operation(operation) { 136 | if (this.tr_buffer) { throw new Error("already finalized"); } 137 | assert(operation, "operation"); 138 | if (!Array.isArray(operation)) { 139 | throw new Error("Expecting array [operation_id, operation]"); 140 | } 141 | this.operations.push(operation); 142 | return; 143 | } 144 | 145 | get_type_operation(name, operation) { 146 | if (this.tr_buffer) { throw new Error("already finalized"); } 147 | assert(name, "name"); 148 | assert(operation, "operation"); 149 | var _type = ops[name]; 150 | assert(_type, `Unknown operation ${name}`); 151 | var operation_id = ChainTypes.operations[_type.operation_name]; 152 | if (operation_id === undefined) { 153 | throw new Error(`unknown operation: ${_type.operation_name}`); 154 | } 155 | if (!operation.fee) { 156 | operation.fee = { amount: 0, asset_id: 1 }; 157 | } 158 | if (name === "proposal_create") { 159 | /* 160 | * Proposals involving the committee account require a review 161 | * period to be set, look for them here 162 | */ 163 | let requiresReview = false, extraReview = 0; 164 | operation.proposed_ops.forEach(op => { 165 | const COMMITTE_ACCOUNT = 0; 166 | let key; 167 | 168 | switch (op.op[0]) { 169 | case 0: // transfer 170 | key = "from"; 171 | break; 172 | 173 | case 6: //account_update 174 | case 17: // asset_settle 175 | key = "account"; 176 | break; 177 | 178 | case 10: // asset_create 179 | case 11: // asset_update 180 | case 12: // asset_update_bitasset 181 | case 13: // asset_update_feed_producers 182 | case 14: // asset_issue 183 | case 18: // asset_global_settle 184 | case 43: // asset_claim_fees 185 | key = "issuer"; 186 | break; 187 | 188 | case 15: // asset_reserve 189 | key = "payer"; 190 | break; 191 | 192 | case 16: // asset_fund_fee_pool 193 | key = "from_account"; 194 | break; 195 | 196 | case 22: // proposal_create 197 | case 23: // proposal_update 198 | case 24: // proposal_delete 199 | key = "fee_paying_account"; 200 | break; 201 | 202 | case 31: // committee_member_update_global_parameters 203 | requiresReview = true; 204 | extraReview = 60 * 60 * 24 * 13; // Make the review period 2 weeks total 205 | break; 206 | } 207 | if (key in op.op[1] && op.op[1][key] === COMMITTE_ACCOUNT) { 208 | requiresReview = true; 209 | } 210 | }); 211 | operation.expiration_time || (operation.expiration_time = (base_expiration_sec() + ChainConfig.expire_in_secs_proposal)); 212 | if (requiresReview) { 213 | operation.review_period_seconds = extraReview + Math.max(committee_min_review, 24 * 60 * 60 || ChainConfig.review_in_secs_committee); 214 | /* 215 | * Expiration time must be at least equal to 216 | * now + review_period_seconds, so we add one hour to make sure 217 | */ 218 | operation.expiration_time += (60 * 60 + extraReview); 219 | } 220 | } 221 | var operation_instance = _type.fromObject(operation); 222 | return [operation_id, operation_instance]; 223 | } 224 | 225 | /* optional: fetch the current head block */ 226 | 227 | update_head_block() { 228 | return Promise.all([ 229 | Apis.instance().db_api().exec("get_objects", [["2.0.0"]]), 230 | Apis.instance().db_api().exec("get_objects", [["2.1.0"]]) 231 | ]).then(function (res) { 232 | let [g, r] = res; 233 | head_block_time_string = r[0].time; 234 | committee_min_review = g[0].parameters.committee_proposal_review_period; 235 | }); 236 | } 237 | 238 | /** optional: there is a deafult expiration */ 239 | set_expire_seconds(sec) { 240 | if (this.tr_buffer) { throw new Error("already finalized"); } 241 | return this.expiration = base_expiration_sec() + sec; 242 | } 243 | 244 | /* Wraps this transaction in a proposal_create transaction */ 245 | propose(proposal_create_options) { 246 | if (this.tr_buffer) { 247 | throw new Error("already finalized"); 248 | } 249 | if (!this.operations.length) { 250 | throw new Error("add operation first"); 251 | } 252 | 253 | assert(proposal_create_options, "proposal_create_options"); 254 | assert(proposal_create_options.fee_paying_account, "proposal_create_options.fee_paying_account"); 255 | 256 | let proposed_ops = this.operations.map(op => { 257 | return { op: op }; 258 | }); 259 | 260 | this.operations = []; 261 | this.signatures = []; 262 | this.signer_private_keys = []; 263 | proposal_create_options.proposed_ops = proposed_ops; 264 | this.add_type_operation("proposal_create", proposal_create_options); 265 | return this; 266 | } 267 | 268 | has_proposed_operation() { 269 | let hasProposed = false; 270 | for (var i = 0; i < this.operations.length; i++) { 271 | if ("proposed_ops" in this.operations[i][1]) { 272 | hasProposed = true; 273 | break; 274 | } 275 | } 276 | 277 | return hasProposed; 278 | } 279 | 280 | /** optional: the fees can be obtained from the witness node */ 281 | set_required_fees(asset_id) { 282 | var fee_pool; 283 | if (this.tr_buffer) { throw new Error("already finalized"); } 284 | if (!this.operations.length) { throw new Error("add operations first"); } 285 | var operations = []; 286 | for (var i = 0, op; i < this.operations.length; i++) { 287 | op = this.operations[i]; 288 | operations.push(ops.operation.toObject(op)); 289 | } 290 | 291 | if (!asset_id) { 292 | var op1_fee = operations[0][1].fee; 293 | if (op1_fee && op1_fee.asset_id !== null) { 294 | asset_id = op1_fee.asset_id; 295 | } else { 296 | asset_id = "1.3.1"; 297 | } 298 | } 299 | 300 | var promises = [ 301 | Apis.instance().db_api().exec("get_required_fees", [operations, asset_id]) 302 | ]; 303 | 304 | 305 | let feeAssetPromise = null; 306 | if (asset_id !== "1.3.1") { 307 | // This handles the fallback to paying fees in BTS if the fee pool is empty. 308 | promises.push(Apis.instance().db_api().exec("get_required_fees", [operations, "1.3.1"])); 309 | promises.push(Apis.instance().db_api().exec("get_objects", [[asset_id]])); 310 | } 311 | 312 | return Promise.all(promises).then((results) => { 313 | 314 | let [fees, coreFees, asset] = results; 315 | asset = asset ? asset[0] : null; 316 | 317 | let dynamicPromise = (asset_id !== "1.3.1" && asset) ? Apis.instance().db_api().exec("get_objects", [[asset.dynamic_asset_data_id]]) : new Promise(function (resolve, reject) { resolve(); }); 318 | 319 | return dynamicPromise.then((dynamicObject) => { 320 | if (asset_id !== "1.3.1") { 321 | fee_pool = dynamicObject ? dynamicObject[0].fee_pool : 0; 322 | let totalFees = 0; 323 | for (let j = 0, fee; j < coreFees.length; j++) { 324 | fee = coreFees[j]; 325 | totalFees += fee.amount; 326 | } 327 | 328 | if (totalFees > parseInt(fee_pool, 10)) { 329 | fees = coreFees; 330 | asset_id = "1.3.1"; 331 | } 332 | } 333 | 334 | // Proposed transactions need to be flattened 335 | var flat_assets = []; 336 | var flatten = function (obj) { 337 | if (Array.isArray(obj)) { 338 | for (var k = 0, item; k < obj.length; k++) { 339 | item = obj[k]; 340 | flatten(item); 341 | } 342 | } else { 343 | flat_assets.push(obj); 344 | } 345 | return; 346 | }; 347 | flatten(fees); 348 | 349 | var asset_index = 0; 350 | 351 | var set_fee = operation => { 352 | if (!operation.fee || operation.fee.amount === 0 353 | || (operation.fee.amount.toString && operation.fee.amount.toString() === "0")// Long 354 | ) { 355 | operation.fee = flat_assets[asset_index]; 356 | // console.log("new operation.fee", operation.fee) 357 | } else { 358 | // console.log("old operation.fee", operation.fee) 359 | } 360 | asset_index++; 361 | if (operation.proposed_ops) { 362 | var result = []; 363 | for (var y = 0; y < operation.proposed_ops.length; y++) 364 | result.push(set_fee(operation.proposed_ops[y].op[1])); 365 | 366 | return result; 367 | } 368 | }; 369 | for (let i = 0; i < this.operations.length; i++) { 370 | set_fee(this.operations[i][1]); 371 | } 372 | }); 373 | //DEBUG console.log('... get_required_fees',operations,asset_id,flat_assets) 374 | }); 375 | } 376 | 377 | 378 | get_potential_signatures() { 379 | var tr_object = ops.signed_transaction.toObject(this); 380 | return Promise.all([ 381 | Apis.instance().db_api().exec("get_potential_signatures", [tr_object]), 382 | Apis.instance().db_api().exec("get_potential_address_signatures", [tr_object]) 383 | ]).then(function (results) { 384 | return { pubkeys: results[0], addys: results[1] }; 385 | } 386 | ); 387 | } 388 | 389 | get_required_signatures(available_keys) { 390 | if (!available_keys.length) { return Promise.resolve([]); } 391 | var tr_object = ops.signed_transaction.toObject(this); 392 | //DEBUG console.log('... tr_object',tr_object) 393 | return Apis.instance().db_api().exec("get_required_signatures", [tr_object, available_keys]).then(function (required_public_keys) { 394 | //DEBUG console.log('... get_required_signatures',required_public_keys) 395 | return required_public_keys; 396 | }); 397 | } 398 | 399 | add_signer(private_key, public_key = private_key.toPublicKey()) { 400 | assert(private_key.d, "required PrivateKey object"); 401 | 402 | if (this.signed) { throw new Error("already signed"); } 403 | if (!public_key.Q) { 404 | public_key = PublicKey.fromPublicKeyString(public_key); 405 | } 406 | // prevent duplicates 407 | let spHex = private_key.toHex(); 408 | for (let sp of this.signer_private_keys) { 409 | if (sp[0].toHex() === spHex) 410 | return; 411 | } 412 | this.signer_private_keys.push([private_key, public_key]); 413 | } 414 | 415 | sign(chain_id = Apis.instance().chain_id) { 416 | return new Promise(async (resolve, reject)=>{ 417 | if (!this.tr_buffer) { throw new Error("not finalized"); } 418 | if (this.signed) { throw new Error("already signed"); } 419 | 420 | if (!this.signProvider) { 421 | if (!this.signer_private_keys.length) { 422 | throw new Error("Transaction was not signed. Do you have a private key? [no_signers]"); 423 | } 424 | var end = this.signer_private_keys.length; 425 | for (var i = 0; 0 < end ? i < end : i > end; 0 < end ? i++ : i++) { 426 | var [private_key, public_key] = this.signer_private_keys[i]; 427 | var sig = Signature.signBuffer( 428 | Buffer.concat([new Buffer(chain_id, "hex"), this.tr_buffer]), 429 | private_key, 430 | public_key 431 | ); 432 | this.signatures.push(sig.toBuffer()); 433 | } 434 | } else { 435 | try{ 436 | this.signatures = await this.signProvider(this, chain_id); 437 | }catch(err){ 438 | reject(err); 439 | return; 440 | } 441 | } 442 | 443 | this.signer_private_keys = []; 444 | this.signed = true; 445 | resolve(); 446 | }); 447 | } 448 | 449 | serialize() { 450 | return ops.signed_transaction.toObject(this); 451 | } 452 | 453 | toObject() { 454 | return ops.signed_transaction.toObject(this); 455 | } 456 | 457 | broadcast(was_broadcast_callback) { 458 | if (this.tr_buffer) { 459 | return this._broadcast(was_broadcast_callback); 460 | } else { 461 | return this.finalize().then(() => { 462 | return this._broadcast(was_broadcast_callback); 463 | }); 464 | } 465 | } 466 | } 467 | 468 | var base_expiration_sec = () => { 469 | var head_block_sec = Math.ceil(getHeadBlockDate().getTime() / 1000); 470 | var now_sec = Math.ceil(Date.now() / 1000); 471 | // The head block time should be updated every 3 seconds. If it isn't 472 | // then help the transaction to expire (use head_block_sec) 473 | if (now_sec - head_block_sec > 30) { return head_block_sec; } 474 | // If the user's clock is very far behind, use the head block time. 475 | return Math.max(now_sec, head_block_sec); 476 | }; 477 | 478 | function _broadcast(was_broadcast_callback) { 479 | return new Promise(async (resolve, reject) => { 480 | try{ 481 | if (!this.signed) { await this.sign(); } 482 | }catch(err){ 483 | reject(err); 484 | return; 485 | } 486 | if (!this.tr_buffer) { throw new Error("not finalized"); } 487 | if (!this.signatures.length) { throw new Error("not signed"); } 488 | if (!this.operations.length) { throw new Error("no operations"); } 489 | 490 | var tr_object = ops.signed_transaction.toObject(this); 491 | // console.log('... broadcast_transaction_with_callback !!!') 492 | Apis.instance().network_api().exec("broadcast_transaction_with_callback", [function (res) { return resolve(res); }, tr_object]).then(function () { 493 | //console.log('... broadcast success, waiting for callback') 494 | if (was_broadcast_callback) was_broadcast_callback(); 495 | return; 496 | } 497 | ).catch((error) => { 498 | // console.log may be redundant for network errors, other errors could occur 499 | console.log(error); 500 | var message = error.message; 501 | if (!message) { message = ""; } 502 | reject(new Error(( 503 | message + "\n" + 504 | "gxb-crypto " + 505 | " digest " + hash.sha256(this.tr_buffer).toString("hex") + 506 | " transaction " + this.tr_buffer.toString("hex") + 507 | " " + JSON.stringify(tr_object))) 508 | ); 509 | return; 510 | } 511 | ); 512 | return; 513 | }); 514 | } 515 | 516 | function getHeadBlockDate() { 517 | return timeStringToDate(head_block_time_string); 518 | } 519 | 520 | function timeStringToDate(time_string) { 521 | if (!time_string) return new Date("1970-01-01T00:00:00.000Z"); 522 | if (! /Z$/.test(time_string)) //does not end in Z 523 | // https://github.com/cryptonomex/graphene/issues/368 524 | time_string = time_string + "Z"; 525 | return new Date(time_string); 526 | } 527 | 528 | export default TransactionBuilder; 529 | --------------------------------------------------------------------------------