[WIP] update Transaction and Account.signTransaction

@types
neeboo 6 years ago
parent 66d558c813
commit b1ca749fb7
  1. 40
      examples/testNode.js
  2. 4
      packages/harmony-account/package.json
  3. 75
      packages/harmony-account/src/account.ts
  4. 17
      packages/harmony-account/src/utils.ts
  5. 7
      packages/harmony-account/src/wallet.ts
  6. 4
      packages/harmony-account/tsconfig.json
  7. 6
      packages/harmony-crypto/src/index.ts
  8. 1
      packages/harmony-network/src/rpc.ts
  9. 1
      packages/harmony-transaction/src/index.ts
  10. 158
      packages/harmony-transaction/src/transaction.ts
  11. 13
      packages/harmony-transaction/src/types.ts
  12. 0
      packages/harmony-transaction/src/utils.ts
  13. 3
      tsconfig.json

@ -1,8 +1,11 @@
const { Account, Wallet } = require('@harmony/account');
const { isAddress, isPrivateKey, numberToHex } = require('@harmony/utils');
const { HttpProvider, Messenger } = require('@harmony/network');
const { Transaction } = require('@harmony/transaction');
const { hexlify, BN } = require('@harmony/crypto');
const wallet = new Wallet();
const msgr = new Messenger(new HttpProvider('https://dev-api.zilliqa.com'));
const wallet = new Wallet(msgr);
async function testEncrypt() {
const mne = wallet.generateMnemonic();
@ -27,7 +30,7 @@ async function testEncrypt() {
console.log(`${encrypted.privateKey}`);
}
testEncrypt();
// testEncrypt();
async function testSign(prvKey, data) {
const newAcc = wallet.addByPrivateKey(prvKey);
@ -36,7 +39,34 @@ async function testSign(prvKey, data) {
console.log(result);
}
testSign(
'0x0123456789012345678901234567890123456789012345678901234567890123',
'0x06',
// testSign(
// '0x0123456789012345678901234567890123456789012345678901234567890123',
// '0x06',
// );
const txn = new Transaction({
to: '0xaa4cf82d745c3ead6c8275d782d6cb0a6beac617',
data: '0x',
gasLimit: '0xbfa8a6d03fa6',
gasPrice: '0x',
value: '0x7a92fecc67512737',
nonce: '0xf8',
chainId: 0,
});
const acc = wallet.addByPrivateKey(
'0xc3886f791236bf31fe8fd7522a7b12808700deb9c159826fc99236c74614118b',
);
console.log(txn.getRLPUnsigned()[0]);
const signed = wallet
.getAccount(acc.address)
.signTransaction(txn, false)
.then(console.log);
// console.log(wallet.messenger);
// console.log(hexlify(0));
// 0xda8003049401234567890123456789012345678901234567890580;
// 0xda8003049401234567890123456789012345678901234567890580;

@ -7,7 +7,7 @@
"browser": "dist/index.js",
"module": "dist/index.esm.js",
"jsnext:main": "dist/index.esm.js",
"typings":"dist/index.d.ts",
"typings": "dist/index.d.ts",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
@ -15,6 +15,8 @@
"license": "ISC",
"dependencies": {
"@harmony/crypto": "^0.0.1",
"@harmony/network": "^0.0.1",
"@harmony/transaction": "^0.0.1",
"@harmony/utils": "^0.0.1"
}
}

@ -7,13 +7,14 @@ import {
decrypt,
EncryptOptions,
Keystore,
sign,
isSignature,
Signature,
} from '@harmony/crypto';
import { isPrivateKey, add0xToString } from '@harmony/utils';
import { isPrivateKey, add0xToString, hexToNumber } from '@harmony/utils';
import { Transaction } from '@harmony/transaction';
import { Messenger, RPCMethod } from '@harmony/network';
import { Shards, ShardId } from './types';
import { RLPSign } from './utils';
class Account {
/**
@ -37,7 +38,10 @@ class Account {
privateKey?: string;
publicKey?: string;
address?: string;
balance?: string = '0';
nonce?: number = 0;
shards: Shards = new Map().set('default', '');
messenger?: Messenger;
/**
* @function checksumAddress checsumAddress getter
@ -55,7 +59,8 @@ class Account {
return this.shards.size;
}
constructor(key?: string) {
constructor(key?: string, messenger?: Messenger) {
this.messenger = messenger;
if (!key) {
this._new();
} else {
@ -108,9 +113,22 @@ class Account {
* @function getBalance get Account's balance
* @return {type} {description}
*/
async getBalance(): Promise<string> {
// console.log()
return '';
async getBalance(): Promise<object> {
if (this.messenger) {
const result = await this.messenger.send(
RPCMethod.GetBalance,
this.address,
);
if (result.responseType === 'result') {
this.balance = hexToNumber(result.balance);
this.nonce = Number.parseInt(hexToNumber(result.nonce), 10);
}
}
return {
balance: this.balance,
nonce: this.nonce,
shards: this.shards,
};
}
/**
@ -120,22 +138,39 @@ class Account {
async updateShards(): Promise<string> {
return '';
}
/**
* @function sign
* @return {Promise<void>} sign transaction
*/
async sign(message: string): Promise<Signature> {
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');
}
async signTransaction(
transaction: Transaction,
updateNonce: boolean = true,
encodeMode: string = 'rlp',
): Promise<Transaction> {
if (!this.privateKey || !isPrivateKey(this.privateKey)) {
throw new Error(`${this.privateKey} is not found or not correct`);
}
// let signed = '';
if (updateNonce) {
const balanceObject: any = await this.getBalance();
transaction.setParams({
...transaction.txParams,
nonce: balanceObject.nonce + 1,
});
}
if (encodeMode === 'rlp') {
const [signature, txnHash]: [Signature, string] = RLPSign(
transaction,
this.privateKey,
);
return transaction.map((obj: any) => {
return { ...obj, signature, txnHash };
});
} else {
throw new Error('Privatekey not found or not correct');
// TODO: if we use other encode method, eg. protobuf, we should implement this
return transaction;
}
}
setMessenger(messenger?: Messenger) {
this.messenger = messenger;
}
/**
* @function _new private method create Account
* @return {Account} Account instance

@ -0,0 +1,17 @@
import { Transaction, TxParams } from '@harmony/transaction';
import { sign, keccak256, Signature } from '@harmony/crypto';
export const RLPSign = (
transaction: Transaction,
prv: string,
): [Signature, string] => {
const [unsignedTxnHash, raw] = transaction.getRLPUnsigned();
const regroup: TxParams = {
...transaction.txParams,
unsignedTxnHash,
};
transaction.setParams(regroup);
const signature = sign(keccak256(unsignedTxnHash), prv);
const signed = transaction.getRLPSigned(raw, signature);
return [signature, signed];
};

@ -1,8 +1,10 @@
import { bip39, hdkey, EncryptOptions } from '@harmony/crypto';
import { Messenger } from '@harmony/network';
import { isPrivateKey } from '@harmony/utils';
import { Account } from './account';
class Wallet {
messenger?: Messenger;
/**
* @memberof Wallet
*
@ -15,6 +17,10 @@ class Wallet {
get accounts(): string[] {
return [...this.accountMap.keys()];
}
constructor(messenger?: Messenger) {
this.messenger = messenger;
}
/**
* @function generateMnemonic
* @memberof Wallet
@ -51,6 +57,7 @@ class Wallet {
addByPrivateKey(privateKey: string): Account {
try {
const newAcc = Account.add(privateKey);
newAcc.setMessenger(this.messenger);
if (newAcc.address) {
this.accountMap.set(newAcc.address, newAcc);
return newAcc;

@ -7,6 +7,8 @@
"include": ["src", "../../typings/**/*.d.ts"],
"references": [
{ "path": "../harmony-crypto" },
{ "path": "../harmony-utils" }
{ "path": "../harmony-utils" },
{ "path": "../harmony-transaction" },
{ "path": "../harmony-network" }
]
}

@ -1,12 +1,16 @@
import hdkey from 'hdkey';
import bip39 from 'bip39';
import BN from 'bn.js';
export * from './random';
export * from './keyTool';
export * from './keystore';
export * from './bytes';
export * from './rlp';
export * from './keccak256';
export * from './errors';
// export types
export * from './types';
export { hdkey, bip39 };
export { hdkey, bip39, BN };

@ -1,6 +1,7 @@
export const enum RPCMethod {
// account related
FetchBalance = 'FetchBalance',
GetBalance = 'GetBalance',
// block info related
GetLatestBlock = 'GetLatestBlock',
GetBlock = 'GetBlock',

@ -1,2 +1,3 @@
export * from './factory';
export * from './transaction';
export * from './types';

@ -1,2 +1,158 @@
class Transaction {}
import {
BN,
encode,
// toChecksumAddress,
arrayify,
hexlify,
stripZeros,
Signature,
splitSignature,
} from '@harmony/crypto';
import { add0xToString } from '@harmony/utils';
import { TxParams } from './types';
export const transactionFields = [
{ name: 'nonce', length: 32, fix: false },
{ name: 'gasPrice', length: 32, fix: false, transform: 'hex' },
{ name: 'gasLimit', length: 32, fix: false, transform: 'hex' },
{ name: 'to', length: 20, fix: true },
{ name: 'value', length: 32, fix: false, transform: 'hex' },
{ name: 'data', fix: false },
];
class Transaction {
// private hash?: string;
// private from?: string;
private nonce: number | string;
private to: string;
private gasLimit: BN;
private gasPrice: BN;
private data: string;
private value: BN;
private chainId: number;
private txnHash: string;
private unsignedTxnHash: string;
private signature: Signature;
// private r?: string;
// private s?: string;
// private v?: number;
// constructor
constructor(params?: TxParams) {
this.nonce = params ? params.nonce : 0;
this.gasPrice = params ? params.gasPrice : new BN(0);
this.gasLimit = params ? params.gasLimit : new BN(0);
this.to = params ? params.to : '0x';
this.value = params ? params.value : new BN(0);
this.data = params ? params.data : '0x';
this.chainId = params ? params.chainId : 0;
this.txnHash = params ? params.txnHash : '0x';
this.unsignedTxnHash = params ? params.unsignedTxnHash : '0x';
this.signature = params
? params.signature
: {
r: '',
s: '',
recoveryParam: 0,
v: 0,
};
}
getRLPUnsigned(): [string, any[]] {
const raw: Array<string | Uint8Array> = [];
transactionFields.forEach((field: any) => {
let value = (<any>this.txParams)[field.name] || [];
value = arrayify(
hexlify(
field.transform === 'hex'
? add0xToString(value.toString('hex'))
: value,
),
);
// Fixed-width field
if (
field.fix === true &&
field.length &&
value.length !== field.length &&
value.length > 0
) {
throw new Error(`invalid length for ${field.name}`);
}
// Variable-width (with a maximum)
if (field.fix === false && field.length) {
value = stripZeros(value);
if (value.length > field.length) {
throw new Error(`invalid length for ${field.name}`);
}
}
raw.push(hexlify(value));
});
if (this.txParams.chainId != null && this.txParams.chainId !== 0) {
raw.push(hexlify(this.txParams.chainId));
raw.push('0x');
raw.push('0x');
}
return [encode(raw), raw];
}
getRLPSigned(raw: any[], signature: Signature): string {
const sig = splitSignature(signature);
let v = 27 + (sig.recoveryParam || 0);
if (raw.length === 9) {
raw.pop();
raw.pop();
raw.pop();
v += this.chainId * 2 + 8;
}
raw.push(hexlify(v));
raw.push(stripZeros(arrayify(sig.r) || []));
raw.push(stripZeros(arrayify(sig.s) || []));
return encode(raw);
}
get txParams(): TxParams {
return {
nonce: this.nonce || 0,
gasPrice: this.gasPrice || new BN(0),
gasLimit: this.gasLimit || new BN(0),
to: this.to || '0x',
value: this.value || new BN(0),
data: this.data || '0x',
chainId: this.chainId || 0,
txnHash: this.txnHash || '0x',
unsignedTxnHash: this.unsignedTxnHash || '0x',
signature: this.signature || '0x',
};
}
setParams(params: TxParams) {
this.nonce = params ? params.nonce : 0;
this.gasPrice = params ? params.gasPrice : new BN(0);
this.gasLimit = params ? params.gasLimit : new BN(0);
this.to = params ? params.to : '0x';
this.value = params ? params.value : new BN(0);
this.data = params ? params.data : '0x';
this.chainId = params ? params.chainId : 0;
this.txnHash = params ? params.txnHash : '0x';
this.unsignedTxnHash = params ? params.unsignedTxnHash : '0x';
this.signature = params
? params.signature
: {
r: '',
s: '',
recoveryParam: 0,
v: 0,
};
}
map(fn: any) {
const newParams = fn(this.txParams);
this.setParams(newParams);
return this;
}
}
export { Transaction };

@ -0,0 +1,13 @@
import { BN, Signature } from '@harmony/crypto';
export interface TxParams {
to: string;
nonce: number | string;
gasLimit: BN;
gasPrice: BN;
data: string;
value: BN;
chainId: number;
txnHash: string;
unsignedTxnHash: string;
signature: Signature;
}

@ -4,6 +4,7 @@
{ "path": "packages/harmony-account" },
{ "path": "packages/harmony-crypto" },
{ "path": "packages/harmony-utils" },
{ "path": "packages/harmony-network" }
{ "path": "packages/harmony-network" },
{ "path": "packages/harmony-transaction" }
]
}

Loading…
Cancel
Save