diff --git a/examples/testNode.js b/examples/testNode.js index 7e9c666..470630b 100644 --- a/examples/testNode.js +++ b/examples/testNode.js @@ -2,9 +2,9 @@ const { Account, Wallet } = require('@harmony/account'); const { isAddress, isPrivateKey, numberToHex } = require('@harmony/utils'); const { HttpProvider, Messenger } = require('@harmony/network'); -async function testEncrypt() { - const wallet = new Wallet(); +const wallet = new Wallet(); +async function testEncrypt() { const mne = wallet.generateMnemonic(); console.log('---hint: please write these down'); @@ -28,3 +28,15 @@ async function testEncrypt() { } testEncrypt(); + +async function testSign(prvKey, data) { + const newAcc = wallet.addByPrivateKey(prvKey); + + const result = await newAcc.sign(data); + console.log(result); +} + +testSign( + '0x0123456789012345678901234567890123456789012345678901234567890123', + '0x06', +); diff --git a/gulpfile.js b/gulpfile.js index efc86f9..b6d74c6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -6,6 +6,7 @@ const packages = [ 'harmony-account', 'harmony-network', 'harmony-utils', + 'harmony-transaction', ]; task('cleanBrowser', async () => { diff --git a/packages/harmony-account/src/account.ts b/packages/harmony-account/src/account.ts index 4c73c89..04fd018 100644 --- a/packages/harmony-account/src/account.ts +++ b/packages/harmony-account/src/account.ts @@ -7,6 +7,9 @@ import { decrypt, EncryptOptions, Keystore, + sign, + isSignature, + Signature, } from '@harmony/crypto'; import { isPrivateKey, add0xToString } from '@harmony/utils'; @@ -118,11 +121,20 @@ class Account { return ''; } /** - * @function signTransaction + * @function sign * @return {Promise} sign transaction */ - async signTransaction(): Promise { - console.log('sign transaction'); + async sign(message: string): Promise { + if (this.privateKey && isPrivateKey(this.privateKey)) { + const signature: Signature = await sign(message, this.privateKey); + if (isSignature(signature)) { + return signature; + } else { + throw new Error('Cannot sign'); + } + } else { + throw new Error('Privatekey not found or not correct'); + } } /** * @function _new private method create Account diff --git a/packages/harmony-account/tsconfig.json b/packages/harmony-account/tsconfig.json index 53ddc87..f517c81 100644 --- a/packages/harmony-account/tsconfig.json +++ b/packages/harmony-account/tsconfig.json @@ -1,8 +1,6 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "baseUrl": ".", - "paths": { "*": ["types/*"] }, "outDir": "dist", "rootDir": "src" }, diff --git a/packages/harmony-crypto/src/bytes.ts b/packages/harmony-crypto/src/bytes.ts index df13895..5dec6e5 100644 --- a/packages/harmony-crypto/src/bytes.ts +++ b/packages/harmony-crypto/src/bytes.ts @@ -336,7 +336,7 @@ export function hexZeroPad(value: string, length: number): string { return value; } -function isSignature(value: any): value is Signature { +export function isSignature(value: any): value is Signature { return value && value.r != null && value.s != null; } diff --git a/packages/harmony-crypto/src/index.ts b/packages/harmony-crypto/src/index.ts index 0eb5021..04a5ef9 100644 --- a/packages/harmony-crypto/src/index.ts +++ b/packages/harmony-crypto/src/index.ts @@ -4,6 +4,7 @@ import bip39 from 'bip39'; export * from './random'; export * from './keyTool'; export * from './keystore'; +export * from './bytes'; // export types export * from './types'; diff --git a/packages/harmony-crypto/src/keyTool.ts b/packages/harmony-crypto/src/keyTool.ts index c091e20..45749fb 100644 --- a/packages/harmony-crypto/src/keyTool.ts +++ b/packages/harmony-crypto/src/keyTool.ts @@ -4,6 +4,7 @@ import * as errors from './errors'; import { keccak256 } from './keccak256'; import { randomBytes } from './random'; +import { isPrivateKey, strip0x } from '@harmony/utils'; const secp256k1 = elliptic.ec('secp256k1'); @@ -48,7 +49,10 @@ export const getAddressFromPrivateKey = (privateKey: string): string => { }; export const getPublic = (privateKey: string, compress?: boolean): string => { - const ecKey = secp256k1.keyFromPrivate(privateKey.slice(2), 'hex'); + if (!isPrivateKey(privateKey)) { + throw new Error(`${privateKey} is not PrivateKey`); + } + const ecKey = secp256k1.keyFromPrivate(strip0x(privateKey), 'hex'); return ecKey.getPublic(compress || false, 'hex'); }; @@ -98,3 +102,20 @@ export const toChecksumAddress = (address: string): string => { return '0x' + chars.join(''); }; + +export const sign = ( + digest: bytes.Arrayish | string, + privateKey: string, +): bytes.Signature => { + if (!isPrivateKey(privateKey)) { + throw new Error(`${privateKey} is not PrivateKey`); + } + const keyPair = secp256k1.keyFromPrivate(strip0x(privateKey), 'hex'); + const signature = keyPair.sign(bytes.arrayify(digest), { canonical: true }); + return { + recoveryParam: signature.recoveryParam, + r: bytes.hexZeroPad('0x' + signature.r.toString(16), 32), + s: bytes.hexZeroPad('0x' + signature.s.toString(16), 32), + v: 27 + signature.recoveryParam, + }; +}; diff --git a/packages/harmony-crypto/src/rlp.ts b/packages/harmony-crypto/src/rlp.ts new file mode 100644 index 0000000..9f9fbd9 --- /dev/null +++ b/packages/harmony-crypto/src/rlp.ts @@ -0,0 +1,159 @@ +// this file is ported from https://github.com/ethers-io/ethers.js/blob/master/src.ts/utils/rlp.ts +// and done some fixes +import { arrayify, hexlify, Arrayish } from './bytes'; + +function arrayifyInteger(value: number): number[] { + const result = []; + while (value) { + result.unshift(value & 0xff); + value >>= 8; + } + return result; +} + +function unarrayifyInteger( + data: Uint8Array, + offset: number, + length: number, +): number { + let result = 0; + for (let i = 0; i < length; i++) { + result = result * 256 + data[offset + i]; + } + return result; +} + +function _encode(object: any[] | string): number[] { + if (Array.isArray(object)) { + let payload: number[] = []; + object.forEach((child) => { + payload = payload.concat(_encode(child)); + }); + + if (payload.length <= 55) { + payload.unshift(0xc0 + payload.length); + return payload; + } + + // tslint:disable-next-line: no-shadowed-variable + const length = arrayifyInteger(payload.length); + length.unshift(0xf7 + length.length); + + return length.concat(payload); + } + + const data: number[] = Array.prototype.slice.call(arrayify(object)); + + if (data.length === 1 && data[0] <= 0x7f) { + return data; + } else if (data.length <= 55) { + data.unshift(0x80 + data.length); + return data; + } + + const length = arrayifyInteger(data.length); + length.unshift(0xb7 + length.length); + + return length.concat(data); +} + +export function encode(object: any): string { + return hexlify(_encode(object)); +} + +interface Decoded { + result: any; + consumed: number; +} + +function _decodeChildren( + data: Uint8Array, + offset: number, + childOffset: number, + length: number, +): Decoded { + const result = []; + + while (childOffset < offset + 1 + length) { + const decoded = _decode(data, childOffset); + + result.push(decoded.result); + + childOffset += decoded.consumed; + if (childOffset > offset + 1 + length) { + throw new Error('invalid rlp'); + } + } + + return { consumed: 1 + length, result }; +} + +// returns { consumed: number, result: Object } +function _decode( + data: Uint8Array, + offset: number, +): { consumed: number; result: any } { + if (data.length === 0) { + throw new Error('invalid rlp data'); + } + + // Array with extra length prefix + if (data[offset] >= 0xf8) { + const lengthLength = data[offset] - 0xf7; + if (offset + 1 + lengthLength > data.length) { + throw new Error('too short'); + } + + const length = unarrayifyInteger(data, offset + 1, lengthLength); + if (offset + 1 + lengthLength + length > data.length) { + throw new Error('to short'); + } + + return _decodeChildren( + data, + offset, + offset + 1 + lengthLength, + lengthLength + length, + ); + } else if (data[offset] >= 0xc0) { + const length = data[offset] - 0xc0; + if (offset + 1 + length > data.length) { + throw new Error('invalid rlp data'); + } + + return _decodeChildren(data, offset, offset + 1, length); + } else if (data[offset] >= 0xb8) { + const lengthLength = data[offset] - 0xb7; + if (offset + 1 + lengthLength > data.length) { + throw new Error('invalid rlp data'); + } + + const length = unarrayifyInteger(data, offset + 1, lengthLength); + if (offset + 1 + lengthLength + length > data.length) { + throw new Error('invalid rlp data'); + } + + const result = hexlify( + data.slice(offset + 1 + lengthLength, offset + 1 + lengthLength + length), + ); + return { consumed: 1 + lengthLength + length, result }; + } else if (data[offset] >= 0x80) { + const length = data[offset] - 0x80; + if (offset + 1 + length > data.length) { + throw new Error('invlaid rlp data'); + } + + const result = hexlify(data.slice(offset + 1, offset + 1 + length)); + return { consumed: 1 + length, result }; + } + return { consumed: 1, result: hexlify(data[offset]) }; +} + +export function decode(data: Arrayish): any { + const bytes = arrayify(data) || new Uint8Array(); + const decoded = _decode(bytes, 0); + if (decoded.consumed !== bytes.length) { + throw new Error('invalid rlp data'); + } + return decoded.result; +} diff --git a/packages/harmony-transaction/package.json b/packages/harmony-transaction/package.json new file mode 100644 index 0000000..5a7f9f7 --- /dev/null +++ b/packages/harmony-transaction/package.json @@ -0,0 +1,20 @@ +{ + "name": "@harmony/transaction", + "version": "0.0.1", + "description": "transaction package for harmony", + "main": "dist/index.js", + "node": "dist/index.js", + "browser": "dist/index.js", + "module": "dist/index.esm.js", + "jsnext:main": "dist/index.esm.js", + "typings": "dist/index.d.ts", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "neeboo@firestack.one", + "license": "ISC", + "dependencies": { + "@harmony/crypto": "^0.0.1", + "@harmony/utils": "^0.0.1" + } +} diff --git a/packages/harmony-transaction/src/factory.ts b/packages/harmony-transaction/src/factory.ts new file mode 100644 index 0000000..4c1044a --- /dev/null +++ b/packages/harmony-transaction/src/factory.ts @@ -0,0 +1,3 @@ +class TransactionFactory {} + +export { TransactionFactory }; diff --git a/packages/harmony-transaction/src/index.ts b/packages/harmony-transaction/src/index.ts new file mode 100644 index 0000000..7cd1838 --- /dev/null +++ b/packages/harmony-transaction/src/index.ts @@ -0,0 +1,2 @@ +export * from './factory'; +export * from './transaction'; diff --git a/packages/harmony-transaction/src/transaction.ts b/packages/harmony-transaction/src/transaction.ts new file mode 100644 index 0000000..6c0e61c --- /dev/null +++ b/packages/harmony-transaction/src/transaction.ts @@ -0,0 +1,2 @@ +class Transaction {} +export { Transaction }; diff --git a/packages/harmony-transaction/tsconfig.json b/packages/harmony-transaction/tsconfig.json new file mode 100644 index 0000000..34bb5ff --- /dev/null +++ b/packages/harmony-transaction/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src", "../../typings/**/*.d.ts"], + "references": [ + { "path": "../harmony-utils" }, + { "path": "../harmony-crypto" } + ] +} diff --git a/scripts/packages.js b/scripts/packages.js index 4e13a93..962b474 100644 --- a/scripts/packages.js +++ b/scripts/packages.js @@ -3,4 +3,5 @@ export default [ 'harmony-crypto', 'harmony-account', 'harmony-network', + 'harmony-transaction', ]; diff --git a/scripts/packagesList.js b/scripts/packagesList.js index c4f995b..ab32034 100644 --- a/scripts/packagesList.js +++ b/scripts/packagesList.js @@ -3,4 +3,5 @@ module.exports = [ { name: 'HarmonyUtils', dest: 'harmony-utils' }, { name: 'HarmonyCrypto', dest: 'harmony-crypto' }, { name: 'HarmonyAccount', dest: 'harmony-account' }, + { name: 'HarmonyTransaction', dest: 'harmony-transaction' }, ]; diff --git a/scripts/packagesTs.ts b/scripts/packagesTs.ts index 2c8b042..88e4f86 100644 --- a/scripts/packagesTs.ts +++ b/scripts/packagesTs.ts @@ -3,6 +3,7 @@ const packages = [ 'harmony-crypto', 'harmony-account', 'harmony-network', + 'harmony-transaction', ]; export { packages }; diff --git a/typings/elliptic.d.ts b/typings/elliptic.d.ts index e85b22f..ad5e127 100644 --- a/typings/elliptic.d.ts +++ b/typings/elliptic.d.ts @@ -45,6 +45,7 @@ declare namespace Elliptic { } interface KeyPair { + sign(arg0: Uint8Array | null, arg1: { canonical: boolean }): any; fromPublic(ec: Curve, pub: BN, enc: string): KeyPair; fromPrivate(ec: Curve, priv: BN, enc: string): KeyPair; // this is broken, but we can't fix it without changing the upstream