dpos-offline
Version:
Offline Signing Transactions for DPOS Blockchains
232 lines (201 loc) • 7.69 kB
text/typescript
import * as bech32 from 'bech32-buffer';
import * as empty from 'is-empty';
import * as Long from 'long';
import { As } from 'type-tagger';
import { toSha256 } from '../utils/sha256';
import { toTransportable } from '../utils/toTransportable';
import { Address, ICoinCodec, ICoinCodecTxs, IKeypair } from './interface';
import { Lisk, LiskCoinCodecMsgs } from './lisk';
import { IBaseTx } from './txs';
import { IRegisterDelegateTx, IRegisterSecondSignature, ISendTx, IVoteTx, LiskTransaction } from './txs/lisk';
import {
IRegisterDelegateRiseV2Tx,
IRegisterSecondSignatureRiseV2Tx,
ISendRiseV2Tx,
IVoteRiseV2Tx, PostableRiseV2Transaction,
riseCodecUtils, RiseIdsHandler,
RiseV2Transaction
} from './txs/rise';
// tslint:disable max-line-length
// tslint:disable-next-line
export type RiseV1Transaction<T> = LiskTransaction<T>;
export type IRiseV2Transaction = IVoteTx<string>
| IVoteRiseV2Tx
| ISendTx
| ISendRiseV2Tx
| IRegisterDelegateTx
| IRegisterDelegateRiseV2Tx
| IRegisterSecondSignature
| IRegisterSecondSignatureRiseV2Tx;
export interface IRiseV2CoinCodecTxs extends ICoinCodecTxs<RiseV2Transaction<any> | LiskTransaction<any>, IRiseV2Transaction, PostableRiseV2Transaction<any>> {
getAddressBytes(address: Address): Buffer;
}
export type IBaseV2Tx = IBaseTx & { kind: string };
export class RiseV2Txs implements IRiseV2CoinCodecTxs {
// tslint:disable-next-line
public _codec: ICoinCodec<this, any>;
public bytes(tx: RiseV2Transaction<any>): Buffer {
return riseCodecUtils.findCodecFromType(tx.type)
.calcFullBytes(tx);
}
public bytesForSignature(tx: RiseV2Transaction<any>): Buffer {
return riseCodecUtils.findCodecFromType(tx.type)
.calcBytes(tx);
}
public calcSignature(tx: RiseV2Transaction<any>, kp: IKeypair | string): Buffer & As<'signature'> {
return this._codec.raw.sign(
toSha256(this.bytesForSignature(tx)),
typeof (kp) === 'string' ? this._codec.deriveKeypair(kp) : kp
);
}
public calc2ndSignature(tx: RiseV2Transaction<any>, kp: IKeypair | string): Buffer & As<'signature'> {
return this._codec.raw.sign(
toSha256(Buffer.concat([this.bytesForSignature(tx), tx.signatures[0]])),
typeof (kp) === 'string' ? this._codec.deriveKeypair(kp) : kp
);
}
public createAndSign<T extends IBaseV2Tx>(tx: T, kp: IKeypair | string): PostableRiseV2Transaction<any>;
public createAndSign<T extends IBaseV2Tx>(tx: T, kp: IKeypair | string, inRawFormat: true): RiseV2Transaction<any>;
public createAndSign<T extends IBaseV2Tx>(tx: T, kp: IKeypair | string, net: 'main'|'test'|'dev', inRawFormat?: true): RiseV2Transaction<any>;
// tslint:disable-next-line variable-name
public createAndSign<T extends IBaseV2Tx>(tx: T, _kp: IKeypair | string, net?: 'main'|'test'|'dev'|true, inRawFormat?: true): PostableRiseV2Transaction<any> | RiseV2Transaction<any> {
const kp = typeof (_kp) === 'string' ? this._codec.deriveKeypair(_kp) : _kp;
if (!net) {
net = 'main';
}
if (net === true) {
net = 'main';
inRawFormat = true;
}
if (empty(tx.sender)) {
tx.sender = {
address : this._codec.calcAddress(kp.publicKey, net),
publicKey: kp.publicKey,
};
}
const signableTx = this.transform(tx, net);
const signedTx = this.sign(signableTx, kp);
if (inRawFormat) {
return signedTx;
}
return this.toPostable(signedTx);
}
public createNonce(): string & As<'nonce'> {
return `${Math.floor(
(Date.now() - Date.UTC(2016, 4, 24, 17, 0, 0, 0)) / 1000
)}` as string & As<'nonce'>;
}
public fromPostable<T>(ptx: PostableRiseV2Transaction<any>): RiseV2Transaction<T> {
return riseCodecUtils.findCodecFromType(ptx.type)
.fromPostable(ptx);
}
public getAddressBytes(address: Address): Buffer {
return RiseIdsHandler.addressToBytes(address);
}
public identifier(tx: RiseV2Transaction<any>): string & As<'txIdentifier'> {
const hash = toSha256(this.bytes(tx));
const temp = [];
for (let i = 0; i < 8; i++) {
temp.push(hash[7 - i]);
}
return Long.fromBytesBE(temp, true).toString() as string & As<'txIdentifier'>;
}
// tslint:disable-next-line variable-name
public sign<T = any>(tx: RiseV2Transaction<T>, _kp: IKeypair | string): RiseV2Transaction<T> {
const kp = typeof (_kp) === 'string' ? this._codec.deriveKeypair(_kp) : _kp;
if (!tx.senderPubData) {
if ([0, 1, 2, 3].indexOf(tx.type) !== -1) {
tx.senderPubData = kp.publicKey;
} else {
tx.senderPubData = Buffer.concat([new Buffer([1]), kp.publicKey]) as Buffer & As<'publicKey'>;
}
}
tx.signatures = tx.signatures || [];
tx.signatures.push(this.calcSignature(tx, kp));
tx.id = this.identifier(tx);
return tx;
}
public toPostable(tx: RiseV2Transaction<any>): PostableRiseV2Transaction<any> {
return {
... toTransportable(tx),
id: this.identifier(tx),
};
}
public transform<B extends IBaseV2Tx, T = any>(tx: B, net: 'main' | 'test' | 'dev' = 'main'): RiseV2Transaction<T> {
tx.sender.address = tx.sender.address || this._codec.calcAddress(tx.sender.publicKey, net, 'v1');
return riseCodecUtils.findCodecFromIdentifier(tx.kind)
.transform(tx);
}
public verify(tx: RiseV2Transaction<any>, signature?: Buffer & As<'signature'>, pubKey?: Buffer & As<'publicKey'>): boolean {
const hash = toSha256(
this.bytesForSignature(tx));
return this._codec.raw.verify(
hash,
signature || tx.signatures[0],
pubKey || this.derivePublicKeyFromPubData(tx.senderPubData)
);
}
public fromV1Format<T>(tx: RiseV1Transaction<T>): RiseV2Transaction<T> {
const asset: any = tx.asset;
if (tx.type === 1 || tx.type === 11) {
asset.signature.publicKey = Buffer.from(asset.signature.publicKey, 'hex');
}
return {
amount: `${tx.amount}`,
asset,
fee: `${tx.fee}`,
id: tx.id,
recipientId: tx.recipientId,
senderId: tx.senderId,
senderPubData: tx.senderPublicKey,
signatures: [
tx.signature,
tx.signSignature,
...(tx.signatures || []),
]
.filter((s) => typeof(s) !== 'undefined' && s !== null),
timestamp: tx.timestamp,
type: tx.type,
version: 0,
};
}
public toV1Format<T>(tx: RiseV2Transaction<T>): RiseV1Transaction<T> {
return {
...tx,
amount: parseInt(tx.amount, 10),
fee: parseInt(tx.fee, 10),
senderPublicKey: tx.senderPubData as Buffer & As<'publicKey'>,
signSignature: tx.signatures[1],
signature: tx.signatures[0],
};
}
private derivePublicKeyFromPubData(pubData: Buffer): Buffer & As<'publicKey'> {
if (pubData.length === 33) {
return pubData.slice(1, 33) as Buffer & As<'publicKey'>;
}
return pubData as Buffer & As<'publicKey'>;
}
}
export const RiseV2: ICoinCodec<RiseV2Txs, LiskCoinCodecMsgs> = {
...Lisk,
msgs: {
...Lisk.msgs,
prefix: new Buffer('RISE Signed Message:\n', 'utf8'),
},
txs: new RiseV2Txs(),
calcAddress(publicKey: (Buffer | string) & As<'publicKey'>, net: 'main' | 'test' | 'dev' = 'main', type: 'v0' | 'v1' = 'v1') {
if (type === 'v0') {
return Lisk.calcAddress(publicKey).replace('L', 'R') as Address;
}
const pubKey = Buffer.isBuffer(publicKey) ? publicKey : Buffer.from(publicKey, 'hex');
return bech32.encode(
net === 'main' ? 'rise' : (net === 'test' ? 'tise' : 'dise'),
Buffer.concat([
Buffer.from([1]),
pubKey,
])
) as string & As<'address'>;
},
};
RiseV2.msgs._codec = RiseV2;
RiseV2.txs._codec = RiseV2;