UNPKG

dpos-offline

Version:

Offline Signing Transactions for DPOS Blockchains

212 lines (187 loc) 6.95 kB
// tslint:disable max-line-length import * as ByteBuffer from 'bytebuffer'; import * as empty from 'is-empty'; import * as Long from 'long'; import { As } from 'type-tagger'; import { Overwrite } from 'utility-types'; import { Address } from '../../interface'; import { IBaseTx, IBaseTxCodec } from '../base'; // tslint:disable-next-line export type LiskTransaction<AssetType> = { recipientId: Address; senderId: Address; amount: number; senderPublicKey: Buffer & As<'publicKey'>; requesterPublicKey?: Buffer & As<'publicKey'>; timestamp: number; fee: number; asset: AssetType; type: number; id: string; signature?: Buffer & As<'signature'>; signSignature?: Buffer & As<'signature'>; signatures?: Array<Buffer & As<'signature'>>; }; export type PostableLiskTransaction<T> = Overwrite<LiskTransaction<T>, { amount: string, fee: string, senderPublicKey: string; requesterPublicKey?: string; signature?: string; signSignature?: string; signatures?: string[]; }>; export abstract class BaseLiskCodec<T> implements IBaseTxCodec<LiskTransaction<T>, PostableLiskTransaction<T>> { public readonly identifier: string; public readonly type: number; public constructor(type: number, identifier: string) { this.type = type; this.identifier = identifier; } public calcBytes(tx: LiskTransaction<T>): Buffer { const assetBytes = this.assetBytes(tx); const bb = new ByteBuffer(1 + 4 + 32 + 32 + 8 + 8 + 64 + 64 + assetBytes.length, true); bb.writeByte(tx.type); bb.writeUint32(tx.timestamp); bb.append(tx.senderPublicKey); if (!empty(tx.requesterPublicKey)) { bb.append(tx.requesterPublicKey); } if (!empty(tx.recipientId)) { bb.append(new Buffer(Long.fromString(tx.recipientId.slice(0, -1)).toBytesBE())); } else { bb.append(Buffer.alloc(8).fill(0)); } // tslint:disable-next-line no-string-literal bb['writeLong'](tx.amount); bb.append(assetBytes); bb.flip(); return new Buffer(bb.toBuffer()); } public calcFullBytes(tx: LiskTransaction<T>): Buffer { return Buffer.concat([ this.calcBytes(tx), ... [ tx.signature, tx.signSignature, ... (tx.signatures || []), ] .filter((el) => !!el), ]); } public fromPostable(ptx: PostableLiskTransaction<T>): LiskTransaction<T> { const toRet: LiskTransaction<T> = { ...ptx, amount : parseInt(`${ptx.amount}`, 10), fee : parseInt(`${ptx.fee}`, 10), requesterPublicKey: (ptx.requesterPublicKey ? Buffer.from(ptx.requesterPublicKey, 'hex') : null) as Buffer & As<'publicKey'>, senderPublicKey : (ptx.senderPublicKey ? Buffer.from(ptx.senderPublicKey, 'hex') : null) as Buffer & As<'publicKey'>, signSignature : (ptx.signSignature ? Buffer.from(ptx.signSignature, 'hex') : null) as Buffer & As<'signature'>, signature : (ptx.signature ? Buffer.from(ptx.signature, 'hex') : null) as Buffer & As<'signature'>, signatures : (ptx.signatures ? ptx.signatures.map((s) => Buffer.from(s, 'hex')) : null) as Array<Buffer & As<'signature'>>, }; ['requesterPublicKey', 'senderPublicKey', 'signSignature', 'signatures', 'signature'] .forEach((k) => { if (toRet[k] === null) { delete toRet[k]; } }); return toRet; } public toPostable(tx: LiskTransaction<T>): PostableLiskTransaction<T> { const toRet: PostableLiskTransaction<T> = { ...tx, amount : `${tx.amount}`, fee : `${tx.fee}`, requesterPublicKey: tx.requesterPublicKey ? tx.requesterPublicKey.toString('hex') : null, senderPublicKey : tx.senderPublicKey.toString('hex'), signSignature : tx.signSignature ? tx.signSignature.toString('hex') : null, signature : tx.signature.toString('hex'), signatures : tx.signatures ? tx.signatures.map((s) => s.toString('hex')) : null, }; ['requesterPublicKey', 'senderPublicKey', 'signSignature', 'signatures'] .forEach((k) => { if (toRet[k] === null) { delete toRet[k]; } }); return toRet; } public transform(from: IBaseTx): LiskTransaction<T> { const toRet: LiskTransaction<T> = { amount : 0, asset : null, fee : null, id : null, recipientId : null, requesterPublicKey: null, senderId : null, senderPublicKey : null, timestamp : null, type : this.type, }; if (empty(from.fee)) { toRet.fee = this.calcFees(from); } else { toRet.fee = parseInt(from.fee, 10); } if (empty(from.sender.publicKey)) { throw new Error('Please set sender publicKey'); } toRet.senderPublicKey = from.sender.publicKey; toRet.senderId = from.sender.address || from.sender.address; if (!empty(from.nonce)) { toRet.timestamp = parseInt(from.nonce, 10); } else { toRet.timestamp = parseInt(this.createNonce(), 10); } // if (tx.kind === 'vote') { // const votes: string[] = []; // for (const pref of tx.preferences) { // votes.push(`${pref.action}${pref.delegateIdentifier.toString('hex')}`); // } // toRet.asset = { votes } as any; // } else if (tx.kind === 'register-delegate') { // toRet.asset = { delegate: { username: tx.identifier } } as any; // } else if (tx.kind === 'second-signature') { // toRet.asset = { signature: { publicKey: tx.publicKey.toString('hex') } } as any; // } else if (tx.kind === 'multisignature') { // toRet.asset = { // multisignature: { // keysgroup: tx.config.added // .map((p) => `+${p.toString('hex')}`) // .concat(tx.config.removed // .map((p) => `-${p.toString('hex')}`) // ), // lifetime : tx.lifetime, // min : tx.min, // }, // } as any; // } else if (tx.kind === 'send') { // if (!empty(tx.memo)) { // toRet.asset = { data: tx.memo } as any; // } // } if (from.signature) { toRet.signature = from.signature; } if (from.extraSignatures) { if (from.extraSignatures.length === 1) { toRet.signSignature = from.extraSignatures[0]; } else { toRet.signatures = from.extraSignatures; } } return toRet; } public abstract calcFees(tx: IBaseTx): number; public createNonce() { return `${Math.floor( (Date.now() - Date.UTC(2016, 4, 24, 17, 0, 0, 0)) / 1000 )}` as string & As<'nonce'>; } public fromBytes(buff: Buffer): LiskTransaction<T> { throw new Error('Cant derive a lsk transaction from buffer inequivocably'); } protected abstract assetBytes(tx: LiskTransaction<T>): Buffer; }