[feat] update bech32

dev
neeboo 5 years ago
parent 7a51fd2bcc
commit b97a9bc617
  1. 11
      packages/harmony-account/src/account.ts
  2. 3
      packages/harmony-contract/src/methods/method.ts
  3. 22
      packages/harmony-core/src/blockchain.ts
  4. 21
      packages/harmony-core/src/harmony.ts
  5. 41
      packages/harmony-crypto/src/address.ts
  6. 234
      packages/harmony-crypto/src/bech32.ts
  7. 2
      packages/harmony-crypto/src/index.ts
  8. 24
      packages/harmony-transaction/src/transaction.ts
  9. 6
      packages/harmony-utils/src/utils.ts
  10. 26
      packages/harmony-utils/src/validators.ts

@ -2,12 +2,13 @@ import {
generatePrivateKey,
getAddressFromPrivateKey,
getPubkeyFromPrivateKey,
toChecksumAddress,
// toChecksumAddress,
encrypt,
decrypt,
EncryptOptions,
Keystore,
Signature,
getAddress,
} from '@harmony-js/crypto';
import { isPrivateKey, add0xToString, hexToNumber } from '@harmony-js/utils';
@ -49,7 +50,13 @@ class Account {
* @return {string} get the checksumAddress
*/
get checksumAddress(): string {
return this.address ? toChecksumAddress(this.address) : '';
return this.address ? getAddress(this.address).checksum : '';
}
get bech32Address(): string {
return this.address ? getAddress(this.address).bech32 : '';
}
get bech32TestNetAddress(): string {
return this.address ? getAddress(this.address).bech32TestNet : '';
}
/**

@ -6,6 +6,7 @@ import {
} from '@harmony-js/transaction';
import { RPCMethod, getResultForData, Emitter } from '@harmony-js/network';
import { hexToNumber, hexToBN } from '@harmony-js/utils';
import { getAddress } from '@harmony-js/crypto';
import { AbiItemModel } from '../models/types';
import { Contract } from '../contract';
import { methodEncoder } from '../utils/encoder';
@ -196,7 +197,7 @@ export class ContractMethod {
}
const txObject = {
...this.params[0],
to: this.contract.address,
to: getAddress(this.contract.address).checksum,
data: this.encodeABI(),
};

@ -16,6 +16,8 @@ import {
DefaultBlockParams,
} from '@harmony-js/utils';
import { getAddress } from '@harmony-js/crypto';
import { Transaction } from '@harmony-js/transaction';
class Blockchain extends HarmonyCore {
@ -40,7 +42,7 @@ class Blockchain extends HarmonyCore {
*
*/
@assertObject({
address: ['isAddress', AssertType.required],
address: ['isValidAddress', AssertType.required],
blockNumber: ['isBlockNumber', AssertType.optional],
})
async getBalance({
@ -52,7 +54,7 @@ class Blockchain extends HarmonyCore {
}) {
const result = await this.messenger.send(
RPCMethod.GetBalance,
[address, blockNumber],
[getAddress(address).checksum, blockNumber],
this.chainPrefix,
);
return this.getRpcResult(result);
@ -201,7 +203,7 @@ class Blockchain extends HarmonyCore {
*
*/
@assertObject({
address: ['isAddress', AssertType.required],
address: ['isValidAddress', AssertType.required],
blockNumber: ['isBlockNumber', AssertType.optional],
})
async getCode({
@ -213,7 +215,7 @@ class Blockchain extends HarmonyCore {
}) {
const result = await this.messenger.send(
RPCMethod.GetCode,
[address, blockNumber],
[getAddress(address).checksum, blockNumber],
this.chainPrefix,
);
return this.getRpcResult(result);
@ -226,7 +228,7 @@ class Blockchain extends HarmonyCore {
}
@assertObject({
address: ['isAddress', AssertType.required],
address: ['isValidAddress', AssertType.required],
position: ['isHex', AssertType.required],
blockNumber: ['isBlockNumber', AssertType.optional],
})
@ -241,14 +243,14 @@ class Blockchain extends HarmonyCore {
}) {
const result = await this.messenger.send(
RPCMethod.GetStorageAt,
[address, position, blockNumber],
[getAddress(address).checksum, position, blockNumber],
this.chainPrefix,
);
return this.getRpcResult(result);
}
@assertObject({
address: ['isAddress', AssertType.required],
address: ['isValidAddress', AssertType.required],
blockNumber: ['isBlockNumber', AssertType.optional],
})
async getTransactionCount({
@ -260,7 +262,7 @@ class Blockchain extends HarmonyCore {
}) {
const result = await this.messenger.send(
RPCMethod.GetTransactionCount,
[address, blockNumber],
[getAddress(address).checksum, blockNumber],
this.chainPrefix,
);
return this.getRpcResult(result);
@ -305,13 +307,13 @@ class Blockchain extends HarmonyCore {
}
@assertObject({
to: ['isAddress', AssertType.optional],
to: ['isValidAddress', AssertType.optional],
data: ['isHex', AssertType.optional],
})
async estimateGas({ to, data }: { to: string; data: string }) {
const result = await this.messenger.send(
RPCMethod.EstimateGas,
[{ to, data }],
[{ to: getAddress(to).checksum, data }],
this.chainPrefix,
);
return this.getRpcResult(result);

@ -7,6 +7,8 @@ import { ContractFactory, Contract } from '@harmony-js/contract';
import { Wallet, Account } from '@harmony-js/account';
import { Blockchain } from './blockchain';
const defaultUrl = 'http://localhost:9500';
export class Harmony extends utils.HarmonyCore {
Modules = {
HttpProvider,
@ -29,16 +31,21 @@ export class Harmony extends utils.HarmonyCore {
private provider: HttpProvider | WSProvider;
constructor(
url: string,
chainType: utils.ChainType = utils.ChainType.Harmony,
chainId: utils.ChainID = utils.ChainID.Default,
{
chainUrl = defaultUrl,
chainType = utils.ChainType.Harmony,
chainId = utils.ChainID.Default,
}: { chainUrl: string; chainType: utils.ChainType; chainId: utils.ChainID },
) {
super(chainType, chainId);
this.provider = utils.isHttp(url)
? new HttpProvider(url)
: utils.isWs(url)
? new WSProvider(url)
: new HttpProvider('http://localhost:9128');
const providerUrl = url || chainUrl;
this.provider = utils.isHttp(providerUrl)
? new HttpProvider(providerUrl)
: utils.isWs(providerUrl)
? new WSProvider(providerUrl)
: new HttpProvider(defaultUrl);
this.messenger = new Messenger(this.provider, this.chainType, this.chainId);
this.blockchain = new Blockchain(this.messenger);
this.transactions = new TransactionFactory(this.messenger);

@ -1,5 +1,11 @@
import {
isAddress,
isBech32Address,
isBech32TestNetAddress,
} from '@harmony-js/utils';
import { toChecksumAddress } from './keyTool';
import { isAddress } from '@harmony-js/utils';
import { fromBech32, toBech32, HRP, tHRP } from './bech32';
export class HarmonyAddress {
// static validator
@ -14,14 +20,33 @@ export class HarmonyAddress {
return toTest.raw === toTest.checksum;
}
// static validator
static isValidBech32(str: string) {
const toTest = new HarmonyAddress(str);
return toTest.raw === toTest.bech32;
}
// static validator
static isValidBech32TestNet(str: string) {
const toTest = new HarmonyAddress(str);
return toTest.raw === toTest.bech32TestNet;
}
raw: string;
basic: string;
get basicHex() {
return `0x${this.basic}`;
}
get checksum() {
return toChecksumAddress(this.basic);
return toChecksumAddress(`0x${this.basic}`);
}
get bech32() {
return toBech32(this.basic, HRP);
}
get bech32TestNet() {
return toBech32(this.basic, tHRP);
}
constructor(raw: string) {
this.raw = raw;
this.basic = this.getBasic(this.raw);
@ -29,11 +54,23 @@ export class HarmonyAddress {
private getBasic(addr: string) {
const basicBool = isAddress(addr);
const bech32Bool = isBech32Address(addr);
const bech32TestNetBool = isBech32TestNetAddress(addr);
if (basicBool) {
return addr.replace('0x', '').toLowerCase();
}
if (bech32Bool) {
const fromB32 = fromBech32(addr, HRP);
return fromB32.replace('0x', '').toLowerCase();
}
if (bech32TestNetBool) {
const fromB32TestNet = fromBech32(addr, tHRP);
return fromB32TestNet.replace('0x', '').toLowerCase();
}
throw new Error(`${addr} is valid address format`);
}
}

@ -0,0 +1,234 @@
import { isAddress } from '@harmony-js/utils';
import { toChecksumAddress } from './keyTool';
// This code is taken from https://github.com/sipa/bech32/tree/bdc264f84014c234e908d72026b7b780122be11f/ref/javascript
// Copyright (c) 2017 Pieter Wuille
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
const polymod = (values: Buffer): number => {
let chk = 1;
// tslint:disable-next-line
for (let p = 0; p < values.length; ++p) {
const top = chk >> 25;
chk = ((chk & 0x1ffffff) << 5) ^ values[p];
for (let i = 0; i < 5; ++i) {
if ((top >> i) & 1) {
chk ^= GENERATOR[i];
}
}
}
return chk;
};
const hrpExpand = (hrp: string): Buffer => {
const ret = [];
let p;
for (p = 0; p < hrp.length; ++p) {
ret.push(hrp.charCodeAt(p) >> 5);
}
ret.push(0);
for (p = 0; p < hrp.length; ++p) {
ret.push(hrp.charCodeAt(p) & 31);
}
return Buffer.from(ret);
};
function verifyChecksum(hrp: string, data: Buffer) {
return polymod(Buffer.concat([hrpExpand(hrp), data])) === 1;
}
function createChecksum(hrp: string, data: Buffer) {
const values = Buffer.concat([
Buffer.from(hrpExpand(hrp)),
data,
Buffer.from([0, 0, 0, 0, 0, 0]),
]);
// var values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);
const mod = polymod(values) ^ 1;
const ret = [];
for (let p = 0; p < 6; ++p) {
ret.push((mod >> (5 * (5 - p))) & 31);
}
return Buffer.from(ret);
}
export const bech32Encode = (hrp: string, data: Buffer) => {
const combined = Buffer.concat([data, createChecksum(hrp, data)]);
let ret = hrp + '1';
// tslint:disable-next-line
for (let p = 0; p < combined.length; ++p) {
ret += CHARSET.charAt(combined[p]);
}
return ret;
};
export const bech32Decode = (bechString: string) => {
let p;
let hasLower = false;
let hasUpper = false;
for (p = 0; p < bechString.length; ++p) {
if (bechString.charCodeAt(p) < 33 || bechString.charCodeAt(p) > 126) {
return null;
}
if (bechString.charCodeAt(p) >= 97 && bechString.charCodeAt(p) <= 122) {
hasLower = true;
}
if (bechString.charCodeAt(p) >= 65 && bechString.charCodeAt(p) <= 90) {
hasUpper = true;
}
}
if (hasLower && hasUpper) {
return null;
}
bechString = bechString.toLowerCase();
const pos = bechString.lastIndexOf('1');
if (pos < 1 || pos + 7 > bechString.length || bechString.length > 90) {
return null;
}
const hrp = bechString.substring(0, pos);
const data = [];
for (p = pos + 1; p < bechString.length; ++p) {
const d = CHARSET.indexOf(bechString.charAt(p));
if (d === -1) {
return null;
}
data.push(d);
}
if (!verifyChecksum(hrp, Buffer.from(data))) {
return null;
}
return { hrp, data: Buffer.from(data.slice(0, data.length - 6)) };
};
// HRP is the human-readable part of zilliqa bech32 addresses
export const HRP = 'hmy';
export const tHRP = 'thmy';
/**
* convertBits
*
* groups buffers of a certain width to buffers of the desired width.
*
* For example, converts byte buffers to buffers of maximum 5 bit numbers,
* padding those numbers as necessary. Necessary for encoding Ethereum-style
* addresses as bech32 ones.
*
* @param {Buffer} data
* @param {number} fromWidth
* @param {number} toWidth
* @param {boolean} pad
* @returns {Buffer|null}
*/
export const convertBits = (
data: Buffer,
fromWidth: number,
toWidth: number,
pad: boolean = true,
) => {
let acc = 0;
let bits = 0;
const ret = [];
const maxv = (1 << toWidth) - 1;
// tslint:disable-next-line
for (let p = 0; p < data.length; ++p) {
const value = data[p];
if (value < 0 || value >> fromWidth !== 0) {
return null;
}
acc = (acc << fromWidth) | value;
bits += fromWidth;
while (bits >= toWidth) {
bits -= toWidth;
ret.push((acc >> bits) & maxv);
}
}
if (pad) {
if (bits > 0) {
ret.push((acc << (toWidth - bits)) & maxv);
}
} else if (bits >= fromWidth || (acc << (toWidth - bits)) & maxv) {
return null;
}
return Buffer.from(ret);
};
/**
* toBech32Address
*
* bech32Encodes a canonical 20-byte Ethereum-style address as a bech32 zilliqa
* address.
*
* The expected format is zil1<address><checksum> where address and checksum
* are the result of bech32 encoding a Buffer containing the address bytes.
*
* @param {string} 20 byte canonical address
* @returns {string} 38 char bech32 bech32Encoded zilliqa address
*/
export const toBech32 = (address: string, useHRP: string = HRP): string => {
if (!isAddress(address)) {
throw new Error('Invalid address format.');
}
const addrBz = convertBits(
Buffer.from(address.replace('0x', ''), 'hex'),
8,
5,
);
if (addrBz === null) {
throw new Error('Could not convert byte Buffer to 5-bit Buffer');
}
return bech32Encode(useHRP, addrBz);
};
/**
* fromBech32Address
*
* @param {string} address - a valid Zilliqa bech32 address
* @returns {string} a canonical 20-byte Ethereum-style address
*/
export const fromBech32 = (address: string, useHRP: string = HRP): string => {
const res = bech32Decode(address);
if (res === null) {
throw new Error('Invalid bech32 address');
}
const { hrp, data } = res;
if (hrp !== useHRP) {
throw new Error(`Expected hrp to be ${useHRP} but got ${hrp}`);
}
const buf = convertBits(data, 5, 8, false);
if (buf === null) {
throw new Error('Could not convert buffer to bytes');
}
return toChecksumAddress('0x' + buf.toString('hex'));
};

@ -9,6 +9,8 @@ export * from './bytes';
export * from './rlp';
export * from './keccak256';
export * from './errors';
export * from './base58';
export * from './bech32';
// export types
export * from './types';

@ -6,6 +6,8 @@ import {
stripZeros,
Signature,
splitSignature,
getAddress,
HarmonyAddress,
} from '@harmony-js/crypto';
import { add0xToString, numberToHex, ChainType } from '@harmony-js/utils';
import {
@ -67,7 +69,9 @@ class Transaction {
this.gasPrice = params ? params.gasPrice : new BN(0);
this.gasLimit = params ? params.gasLimit : new BN(0);
this.shardID = params ? params.shardID : 0;
this.to = params ? params.to : '0x';
// this.to= params ? params.to:'0x';
this.to = params ? this.normalizeAddress(params.to) : '0x';
this.value = params ? params.value : new BN(0);
this.data = params ? params.data : '0x';
// chainid should change with different network settings
@ -187,7 +191,8 @@ class Transaction {
gasPrice: this.gasPrice || new BN(0),
gasLimit: this.gasLimit || new BN(0),
shardID: this.shardID || 0,
to: this.to || '0x',
// to: this.to || '0x',
to: this.normalizeAddress(this.to) || '0x',
value: this.value || new BN(0),
data: this.data || '0x',
chainId: this.chainId || 0,
@ -203,7 +208,8 @@ class Transaction {
this.gasPrice = params ? params.gasPrice : new BN(0);
this.gasLimit = params ? params.gasLimit : new BN(0);
this.shardID = params ? params.shardID : 0;
this.to = params ? params.to : '0x';
// this.to = params ? params.to : '0x';
this.to = params ? this.normalizeAddress(params.to) : '0x';
this.value = params ? params.value : new BN(0);
this.data = params ? params.data : '0x';
this.chainId = params ? params.chainId : 0;
@ -453,5 +459,17 @@ class Transaction {
throw error;
}
}
normalizeAddress(address: string) {
if (
HarmonyAddress.isValidChecksum(address) ||
HarmonyAddress.isValidBech32(address) ||
HarmonyAddress.isValidBech32TestNet(address)
) {
return getAddress(address).checksum;
} else {
throw new Error(`Address format is not supported`);
}
}
}
export { Transaction };

@ -10,6 +10,9 @@ import {
isPublicKey,
isPrivateKey,
isAddress,
isBech32Address,
isBech32TestNetAddress,
isValidAddress,
isHash,
isBlockNumber,
} from './validators';
@ -33,6 +36,9 @@ export const validatorArray: any = {
isAddress: [isAddress],
isHash: [isHash],
isBlockNumber: [isBlockNumber],
isBech32Address: [isBech32Address],
isBech32TestNetAddress: [isBech32TestNetAddress],
isValidAddress: [isValidAddress],
};
export function validateArgs(

@ -159,3 +159,29 @@ export const isBlockNumber = (obj: any): boolean => {
return isHex(obj) || blockParams.some((val) => val === obj);
};
isBlockNumber.validator = 'isBlockNumber';
export const isBech32Address = (raw: string): boolean => {
return !!raw.match(/^hmy1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38}/);
};
isBech32Address.validator = 'isBech32Address';
export const isBech32TestNetAddress = (raw: string): boolean => {
return !!raw.match(/^thmy1[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38}/);
};
isBech32TestNetAddress.validator = 'isBech32TestNetAddress';
export const isValidAddress = (address: string): boolean => {
if (!isString(address)) {
throw new Error(`${address} is not string`);
}
if (
isAddress(address) ||
isBech32Address(address) ||
isBech32TestNetAddress(address)
) {
return true;
} else {
return false;
}
};
isValidAddress.validator = 'isValidAddress';

Loading…
Cancel
Save