Skip to content
This repository was archived by the owner on Dec 5, 2019. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/packages/core/api/__mocks__/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const accountApiInstance = {
getBalance: jest.fn()
}

export const AccountApi = jest.fn().mockImplementation(() => {
return accountApiInstance;
})
31 changes: 31 additions & 0 deletions src/packages/core/api/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as config from 'config';
import { APIServerClass, IAPIConfig } from './APIServer';

export interface ApiBalance {
total: number;
usable: number;
locked: number;
}

export class AccountApi extends APIServerClass {

constructor(conf: IAPIConfig = config.nuls.api.explorer) {
super({ ...config.nuls.api.explorer, ...conf });
}

async getBalance(address): Promise<ApiBalance> {

const resource: string = this.getResource('balance', address);

try {
return (await this.api.get(resource)).data;

} catch (e) {

throw this.handleErrors(e);

}

}

}
2 changes: 1 addition & 1 deletion src/packages/core/common.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const HASH_LENGTH = 34;
export const ADDRESS_LENGTH = 23;
export const P2SH_ADDRESS_TYPE = 3;

export const CONSENSUS_LOCK_TIME = -1;

export const BLACK_HOLE_ADDRESS = 'Nse5FeeiYk1opxdc5RqYpEWkiUDGNuLs';
Expand Down
37 changes: 37 additions & 0 deletions src/packages/core/protocol/__tests__/account.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Account, AddressType, ChainIdType, CustomAddressPosition } from '../../index';
//import { accountApiInstance } from '../../api/__mocks__/account';
import * as accountModule from '../../api/account';
import { randomBytes } from 'crypto';
import { publicKeyCreate, verify } from 'secp256k1';

jest.mock('crypto');
jest.mock('secp256k1');
jest.mock('../../api/account');

describe('create new accounts', () => {

Expand All @@ -13,6 +16,40 @@ describe('create new accounts', () => {

describe('valid private key', () => {

describe('balance', () => {

it('getting the balance of an account', async () => {

// This API (explorer.nuls.services) doesn't currently work for new addresses
// If it would, this is what a test could look like:

// const account = Account.create();

// account.switchChain(ChainIdType.Testnet);
// let balance = await account.getBalance({host: 'https://explorer.nuls.services', base: ''});
// console.log(balance);

// expect(balance).toEqual({
// total: 0,
// locked: 0,
// usable: 0
// });
// Using a pre-defined address..

const accountApiInstance = new accountModule.AccountApi();
accountApiInstance.getBalance.mockResolvedValueOnce({total: 9999999999999, locked: 0, usable: 9999999999999});

const address = 'TTatyig2SCtmUEsgguKvxQQ421e6NULS';
const balance = await Account.getBalance(address, {host: 'https://explorer.nuls.services', base: ''});

expect(balance.locked + balance.usable).toEqual(balance.total);
expect(balance.total).toEqual(9999999999999);
expect(balance.locked).toEqual(0);
expect(balance.usable).toEqual(9999999999999);

});
});

describe('create', () => {

it('creating a new account', () => {
Expand Down
24 changes: 22 additions & 2 deletions src/packages/core/protocol/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import Hex from 'crypto-js/enc-hex';
import Base64 from 'crypto-js/enc-base64';

import * as secp256k1 from 'secp256k1';
import { getPrivateKeyBuffer, getXOR, sha256, ripemd160 } from '../utils';
import { getPrivateKeyBuffer, getXOR, sha256, ripemd160, isValidAddress } from '../utils';


import { IAPIConfig } from '..';
import { AccountApi, ApiBalance } from '../api/account';

export interface AccountObject {
address: string;
Expand All @@ -16,7 +20,8 @@ export interface AccountObject {

export enum AddressType {
Default = 1,
Contract = 2
Contract = 2,
P2SH = 3
}

export enum ChainIdType {
Expand Down Expand Up @@ -65,6 +70,21 @@ export class Account {
/** Public key HEX Buffer */
private publicKeyBuffer: Buffer = Buffer.from([]);

/* Gets the balance of an address (Testnet only for now) */
public static async getBalance(address: string, iapiConfig: IAPIConfig): Promise<ApiBalance> {
if(isValidAddress(address)){
let accountApi = new AccountApi(iapiConfig);
return await accountApi.getBalance(address);
} else {
throw new Error("Invalid Address Used!");
}
}

/* Gets the balance of this account (works for test net only) */
public async getBalance(iapiConfig:IAPIConfig): Promise<ApiBalance> {
return await Account.getBalance(this.address, iapiConfig);
}

/**
* This will loop around until it finds a matching address
* @param str The string to look for in addresses created - The larger the string the harder it is to find
Expand Down
26 changes: 26 additions & 0 deletions src/packages/core/utils/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { isValidAddress } from '../crypto';

jest.unmock('crypto');
jest.unmock('secp256k1');

describe('Address validation checks', () => {

beforeEach(() => {
jest.restoreAllMocks();
});

it('check validity of different addresses', () => {

expect(isValidAddress("TTavFTDgdQNeYgVQBaNTF6SeK54nswH5")).toEqual(true);
expect(isValidAddress("Nsdwnd4auFisFJKU6iDvBxTdPkeg8qkB")).toEqual(true);
expect(isValidAddress("Nse3uLgeCBWP48GCGh8L54gnELfpnSG9")).toEqual(true);
expect(isValidAddress("NseBuUpi4iwbJsj1UrUb4eiAWav9UY4C")).toEqual(true);
expect(isValidAddress("Nse7MZAwVTbdWXxWwgN6vfcPwBYC1izz")).toEqual(true);


expect(isValidAddress("avFTDgdQNeYgVQBaNTF6SeK54nswH5")).toEqual(false);
expect(isValidAddress("TTavFTDgdQNeYgVQBaNTF6SeK54nswH5DXXD")).toEqual(false);
expect(isValidAddress("")).toEqual(false);

});
});
61 changes: 59 additions & 2 deletions src/packages/core/utils/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import * as bs58 from 'bs58';
import RIPEMD160 from 'ripemd160';
import * as secp256k1 from 'secp256k1';
import * as shajs from 'sha.js';
import { HASH_LENGTH } from '../common';
import {
HASH_LENGTH,
ADDRESS_LENGTH,
} from '../common';

import { AddressType, ChainIdType } from '../protocol/account';

import { isHex } from './serialize';

export const PRIVATE_KEY_LENGTH = 64;
Expand Down Expand Up @@ -53,9 +59,47 @@ export function isValidPrivateKey(privateKey: string): boolean {

}

/*
https://github.com/nuls-io/nuls/blob/b8e490a26eeec7b16d924d8398a67ede24ff86ca/core-module/kernel/src/main/java/io/nuls/kernel/utils/AddressTool.java#L77-L117
*/
export function isValidAddress(address: string): boolean {

return /^(Ns|TT)([a-zA-Z-0-9]{30})$/.test(address);
if(!/^(Ns|TT)([a-zA-Z-0-9]{30})$/.test(address))
return false;

let bytes: Buffer;

try {
bytes = Buffer.from(bs58.decode(address));
if(bytes.length != ADDRESS_LENGTH + 1)
return false;
} catch {
return false;
}

let chainId: Number;
let type: Number;

try {
chainId = bytes.readInt16LE(0);
type = bytes.readInt8(2);
} catch {
return false;
}

if (Object.values(ChainIdType).indexOf(chainId) === -1){
return false;
}
if (Object.values(AddressType).indexOf(type) === -1) {
return false;
}
try {
checkXOR(bytes);
} catch {
return false;
}

return true;

}

Expand Down Expand Up @@ -89,6 +133,19 @@ export function getXOR(bytes: Buffer): number {

}

/*
https://github.com/nuls-io/nuls/blob/b8e490a26eeec7b16d924d8398a67ede24ff86ca/core-module/kernel/src/main/java/io/nuls/kernel/utils/AddressTool.java#L169-L183
*/
export function checkXOR(hashs: Buffer) {

const body: Buffer = hashs.slice(0, ADDRESS_LENGTH);
const xor = getXOR(body);

if (xor != hashs[ADDRESS_LENGTH]) {
throw new Error("Address XOR doesn't check out.");
}
}

export function addressFromHash(hash: AddressHash): string {

return bs58.encode(Buffer.concat([hash, Buffer.from([getXOR(hash)])]));
Expand Down