UNPKG

keystore_wdc

Version:

``` npm i keystore_wdc; const KeyStore = require('keystore_wdc'); const ks = new KeyStore(); ``` #### 生成keystore ``` async function create(){ const keystore = await ks.Create("your password"); } ``` * 返回keystore,密码格式不正确返回-1。

397 lines (339 loc) 14.4 kB
import { normalizeAddress, bin2hex, digest, bin2str, convert, dig2BN, address2PublicKeyHash, hex2bin, encodeBE, encodeUint32, toSafeInt, bytesToF64, publicKeyHash2Address } from "./utils" import { ABI, getContractAddress, normalizeParams } from "./contract" import { TransactionResult, Binary, AbiInput, Digital, ABI_DATA_TYPE, ZERO, Readable } from "./types" import { Abort, CallContext, ContextHost, DBHost, EventHost, HashHost, Log, RLPHost, Util, Reflect, Transfer, Uint256Host, AbstractHost } from './hosts' import BN = require('../bn') import * as rlp from './rlp' const utf16Decoder = new TextDecoder('utf-16') const utf8Decoder = new TextDecoder() /** * 虚拟机实例 */ interface VMInstance extends WebAssembly.Instance { exports: { memory: WebAssembly.Memory __alloc: (len: number | bigint, id: number | bigint) => number | bigint __idof: (t: ABI_DATA_TYPE) => number | bigint __retain: (p: number | bigint) => void init?: Function } } /** * 对字符串进行 utf16 编码,用于向 WebAssembly 内存中导入 * @param str */ function strEncodeUTF16(str: string): ArrayBuffer { var buf = new ArrayBuffer(str.length * 2); var bufView = new Uint16Array(buf); for (var i = 0, strLen = str.length; i < strLen; i++) { bufView[i] = str.charCodeAt(i); } return buf } export class MemoryView { view: DataView constructor(mem: WebAssembly.Memory) { this.view = new DataView(mem.buffer) } loadUTF8(offset: number | bigint, length: number | bigint): string { return utf8Decoder.decode(this.loadN(offset, length)) } loadUTF16(offset: number | bigint): string { return utf16Decoder.decode(this.loadBuffer(Number(offset))) } loadU32(offset: number | bigint) { return this.view.getUint32(Number(offset), true) } loadBuffer(offset: number | bigint) { let len = this.loadU32(Number(offset) - 4) return this.loadN(offset, len) } loadN(offset: number | bigint, length: number | bigint): ArrayBuffer { return this.view.buffer.slice(Number(offset), Number(offset) + Number(length)) } put(offset: number | bigint, data: ArrayBuffer): void { new Uint8Array(this.view.buffer).set(new Uint8Array(data), Number(offset)) } } export function isZero(n: number | bigint): boolean { return n === 0 || n === BigInt(0) } /** * virtual machine for chrome debugging */ export class VirtualMachine { // current block height height = 0 // parent block hash parentHash: ArrayBuffer // current block hash hash: ArrayBuffer = (new Uint8Array(32)).buffer // contract address -> url contractCode: Map<string, string> = new Map() // cache for abi abiCache: Map<string, ABI[]> = new Map() // record nonce nonceMap: Map<string, number> = new Map() balanceMap: Map<string, BN> = new Map() // unix epoch seconds now: number storage: Map<string, Map<string, ArrayBuffer>> = new Map() constructor() { if (typeof WebAssembly !== 'object') throw new Error('webassembly not available here') this.nextBlock() } normParams(abi: ABI, params?: AbiInput | AbiInput[] | Record<string, AbiInput>): AbiInput[] { let p = normalizeParams(params) if (Array.isArray(p)) return p const ret = [] abi.inputs.forEach(i => { ret.push(params[i.name]) }) return ret } putParams(instance: VMInstance, abi: ABI, params: AbiInput[] | Record<string, AbiInput>): void { let arr: AbiInput[] if (Array.isArray(params)) { arr = params } else { arr = [] abi.inputs.forEach(x => arr.push(params[x.name])) } for (let i = 0; i < abi.inputs.length; i++) { let t = ABI_DATA_TYPE[abi.inputs[i].type] let id = instance.exports.__idof(t) } } malloc(instance: VMInstance, val: AbiInput, type: ABI_DATA_TYPE): number | bigint { let view = new MemoryView(<WebAssembly.Memory>instance.exports.memory) let data: ArrayBuffer let id = Number(instance.exports.__idof(type)) let offset: number | bigint switch (type) { case ABI_DATA_TYPE.f64: case ABI_DATA_TYPE.bool: case ABI_DATA_TYPE.i64: case ABI_DATA_TYPE.u64: { let converted = convert(val, type) if(type === ABI_DATA_TYPE.f64){ return bytesToF64(<Uint8Array> converted) } if(type == ABI_DATA_TYPE.bool){ return (<BN>converted).toNumber() } let l = <BN>(converted instanceof Uint8Array ? new BN(converted, 10, 'be') : converted) return BigInt(l.toString(10)) } case ABI_DATA_TYPE.string: { let converted = <string>convert(val, type) data = strEncodeUTF16(converted) offset = instance.exports.__alloc(data.byteLength, id) break } case ABI_DATA_TYPE.bytes: { data = (<Uint8Array>convert(val, ABI_DATA_TYPE.bytes)).buffer offset = instance.exports.__alloc(data.byteLength, id) break } case ABI_DATA_TYPE.u256: { let converted = <BN>convert(val, type) let buf = encodeBE(converted).buffer let ptr = this.malloc(instance, buf, ABI_DATA_TYPE.bytes) data = encodeUint32(ptr) offset = instance.exports.__alloc(4, id) break } case ABI_DATA_TYPE.address: { let buf = (<Uint8Array>convert(val, ABI_DATA_TYPE.address)).buffer offset = instance.exports.__alloc(4, id) let ptr = this.malloc(instance, buf, ABI_DATA_TYPE.bytes) data = encodeUint32(ptr) break } } view.put(offset, data) instance.exports.__retain(offset) return offset } alloc(address: Binary, amount: Digital): void { this.balanceMap.set(bin2hex(normalizeAddress(address)), <BN>convert(amount, ABI_DATA_TYPE.u256)) } // 模拟下一区块的生成 nextBlock(): void { this.height++ this.parentHash = this.hash this.hash = digest(rlp.encode(this.height)).buffer this.now = Math.floor((new Date()).valueOf() / 1000) } addBalance(addr: Binary, amount?: Digital) { let hex = bin2hex(normalizeAddress(addr)) let balance = this.balanceMap.get(hex) || ZERO balance = balance.add(dig2BN(amount || ZERO)) this.balanceMap.set(hex, balance) } subBalance(addr: Binary, amount?: Digital) { let hex = bin2hex(normalizeAddress(addr)) let balance = this.balanceMap.get(hex) || ZERO let a = dig2BN(amount || ZERO) if (balance.cmp(a) < 0) throw new Error(`the balance of ${hex} is not enough`) balance = balance.sub(a) this.balanceMap.set(hex, balance) } increaseNonce(sender: Binary): number { let senderHex = bin2hex(normalizeAddress(sender)) const n = (this.nonceMap.get(senderHex) || 0) + 1 this.nonceMap.set(senderHex, n) return n } call(sender: Binary, addr: Binary, method: string, params?: AbiInput | AbiInput[] | Record<string, AbiInput>, amount?: Digital): Promise<Readable> { let origin = normalizeAddress(sender).buffer const n = this.increaseNonce(sender) return this.callInternal(method, { type: null, sender: origin, to: normalizeAddress(addr).buffer, amount: dig2BN(amount || ZERO), nonce: n, origin: origin, txHash: digest(rlp.encode([normalizeAddress(sender), n])).buffer, contractAddress: normalizeAddress(addr).buffer, readonly: false }, params) } private async callInternal(method: string, ctx?: CallContext, params?: AbiInput | AbiInput[] | Record<string, AbiInput>): Promise<Readable> { // 1. substract amount this.subBalance(ctx.sender, ctx.amount) this.addBalance(ctx.contractAddress, ctx.amount) ctx.type = method === 'init' ? 16 : 17 const file = this.contractCode.get(bin2hex(ctx.contractAddress)) const abi = await this.fetchABI(file) let mem = new WebAssembly.Memory({ initial: 10, maximum: 65535 }) const env = { memory: mem, } const hosts: AbstractHost[] = [ new Log(this), new Abort(this), new Util(this), new HashHost(this), new EventHost(this, ctx), new DBHost(this, ctx), new ContextHost(this, ctx), new RLPHost(this), new Reflect(this), new Transfer(this, ctx), new Uint256Host(this) ] hosts.forEach(h => { h.init(env) env[h.name()] = (...args: (number | bigint)[]) => { return h.execute(args) } }) const module = await WebAssembly.compileStreaming(fetch(file)) console.log(module) let instance = <VMInstance>(await WebAssembly.instantiateStreaming(fetch(file), { env: env, })).instance console.log('initialized') if (typeof instance.exports[method] !== 'function') { throw new Error(`call internal failed: ${method} not found`) } const a = abi.filter(x => x.type === 'function' && x.name === method)[0] const arr = this.normParams(a, params) const args = [] for (let i = 0; i < a.inputs.length; i++) { args.push(this.malloc(instance, arr[i], ABI_DATA_TYPE[a.inputs[i].type])) } console.log('call!!!!') let ret = instance.exports[method].apply(window, args) if (a.outputs && a.outputs.length) return this.extractRet(instance, ret, ABI_DATA_TYPE[a.outputs[0].type]) } extractRet(ins: VMInstance, offset: number | bigint, type: ABI_DATA_TYPE): Readable { let ret = this.extractRetInternal(ins, offset, type) if (!(ret instanceof ArrayBuffer)){ return ret } switch (type){ case ABI_DATA_TYPE.bytes: return bin2hex(ret) case ABI_DATA_TYPE.address: return publicKeyHash2Address(ret) case ABI_DATA_TYPE.u256: return toSafeInt(ret) default: throw new Error('unexpected') } } extractRetInternal(ins: VMInstance, offset: number | bigint, type: ABI_DATA_TYPE): boolean | number | string | ArrayBuffer { let view = new MemoryView(ins.exports.memory) switch (type) { case ABI_DATA_TYPE.bool: return Number(offset) !== 0 case ABI_DATA_TYPE.i64: return toSafeInt(offset) case ABI_DATA_TYPE.u64:{ // 即使webassembly 返回类型是 uint, bigint 也会出现小于 0 的情况,需要自行转换 if(offset < 0){ let buf = new ArrayBuffer(8) new DataView(buf).setBigInt64(0, BigInt(offset)) return toSafeInt(buf) } return toSafeInt(offset) } case ABI_DATA_TYPE.f64: { return <number>offset } case ABI_DATA_TYPE.string: { return utf16Decoder.decode(<ArrayBuffer>this.extractRetInternal(ins, offset, ABI_DATA_TYPE.bytes)) } case ABI_DATA_TYPE.bytes: { let len = view.loadU32(Number(offset) - 4) return view.loadN(offset, len) } case ABI_DATA_TYPE.address: case ABI_DATA_TYPE.u256: { let ptr = view.loadU32(offset) return this.extractRetInternal(ins, ptr, ABI_DATA_TYPE.bytes) } } } async view(): Promise<Readable> { return null } // 合约部署 async deploy(sender: Binary, wasmFile: string, parameters?: AbiInput | AbiInput[] | Record<string, AbiInput>, amount?: Digital): Promise<Readable> { let senderAddress = normalizeAddress(sender) // 用 keccak256(rlp([sender, nonce ])) 模拟事务哈希值 计算地址 const n = this.increaseNonce(sender) const txHash = digest(rlp.encode([normalizeAddress(sender), n])) const contractAddress = normalizeAddress(getContractAddress(txHash)) const contractAddressHex = bin2hex(contractAddress) const abi = await this.fetchABI(wasmFile) this.abiCache.set(contractAddressHex, abi) this.contractCode.set(contractAddressHex, wasmFile) const a = abi.filter(x => x.type === 'function' && x.name === 'init')[0] // try to execute init function if (a) { return this.callInternal('init', { type: null, sender: senderAddress, to: new Uint8Array(20).buffer, amount: dig2BN(amount || ZERO), nonce: n, origin: senderAddress, txHash: txHash.buffer, contractAddress: contractAddress.buffer, readonly: false }, parameters) } this.nextBlock() return null } // 根据文件名规范获取 abi async fetchABI(wasmFile: string): Promise<ABI[]> { let f = wasmFile.replace(/^(.*)\.wasm$/, '$1.abi.json') if (this.abiCache.has(f)) return this.abiCache.get(f) const resp = await fetch(f) const buf = await resp.arrayBuffer() return JSON.parse(bin2str(buf)) } }