UNPKG

dpos-offline

Version:

Offline Signing Transactions for DPOS Blockchains

198 lines (177 loc) 6.23 kB
// tslint:disable max-line-length import * as empty from 'is-empty'; import * as Long from 'long'; import { As } from 'type-tagger'; import { Omit, Overwrite } from 'utility-types'; import { encode as encodeVarInt } from 'varuint-bitcoin'; import { toSha256 } from '../utils/sha256'; import { ed25519 } from '../utils/sodium'; import { Address, ICoinCodec, ICoinCodecMsgs, ICoinCodecTxs, IKeypair, SenderType } from './interface'; import { IBaseTx } from './txs/'; import { IRegisterDelegateTx, IRegisterSecondSignature, ISendTx, IVoteTx, liskCodecUtils, LiskTransaction, PostableLiskTransaction } from './txs/lisk'; export interface IRegisterMultisignature extends IBaseTx { readonly kind: 'multisignature'; readonly min: number; readonly lifetime: number; readonly config: { added: Array<Buffer & As<'publicKey'>>; removed: Array<Buffer & As<'publicKey'>>; }; } export type ILiskTransaction = IVoteTx | ISendTx | IRegisterDelegateTx | IRegisterSecondSignature | IRegisterMultisignature; // tslint:disable-next-line export type LiskCoinCodecTxs = ICoinCodecTxs<LiskTransaction<any>, ILiskTransaction, PostableLiskTransaction<any>> & { getAddressBytes(address: Address): Buffer; bytesForSignature(tx: LiskTransaction<any>): Buffer }; export type LiskCoinCodecMsgs = ICoinCodecMsgs & { readonly prefix: Buffer readonly signablePayload: (msg: Buffer | string) => Buffer; }; export const Lisk: ICoinCodec<LiskCoinCodecTxs, LiskCoinCodecMsgs> = { txs: { _codec : null, getAddressBytes(address: Address): Buffer { return new Buffer(Long.fromString(address.slice(0, -1)).toBytesBE()); }, bytes(tx: LiskTransaction<any>) { return liskCodecUtils.findCodecFromType(tx.type) .calcFullBytes(tx); }, bytesForSignature(tx: LiskTransaction<any>): Buffer { return liskCodecUtils .findCodecFromType(tx.type) .calcBytes(tx as any); }, transform<T = any>(tx: ILiskTransaction) { return liskCodecUtils.findCodecFromIdentifier(tx.kind) .transform(tx); }, // tslint:disable-next-line max-line-length calcSignature(tx: LiskTransaction<any>, kp: IKeypair | string) { return this._codec.raw.sign( toSha256(this.bytes(tx)), typeof(kp) === 'string' ? this._codec.deriveKeypair(kp) : kp ); }, // tslint:disable-next-line variable-name createAndSign(tx: Omit<ILiskTransaction, 'sender'> & { sender?: SenderType }, _kp: IKeypair | string, inRawFormat?: true) { const kp = typeof(_kp) === 'string' ? this._codec.deriveKeypair(_kp) : _kp; if (empty(tx.sender)) { tx.sender = { address : this._codec.calcAddress(kp.publicKey), publicKey: kp.publicKey, }; } const signableTx = this.transform(tx); const signedTx = this.sign(signableTx, kp); if (inRawFormat) { return signedTx; } return this.toPostable(signedTx); }, // tslint:disable-next-line variable-name sign(tx: LiskTransaction<any>, _kp: IKeypair | string) { const kp = typeof(_kp) === 'string' ? this._codec.deriveKeypair(_kp) : _kp; if (!tx.senderPublicKey) { tx.senderPublicKey = kp.publicKey; } tx.signature = this.calcSignature(tx, kp, { skipSecondSign: true, skipSignature : true, }); tx.id = this.identifier(tx); return tx; }, verify(tx: LiskTransaction<any>, signature?: Buffer & As<'signature'>, pubKey?: Buffer & As<'publicKey'>): boolean { const hash = toSha256( this.bytesForSignature(tx) ); return this._codec.raw.verify(hash, signature || tx.signature, pubKey || tx.senderPublicKey); }, toPostable<T = any>(tx: LiskTransaction<T>): PostableLiskTransaction<T> { return { ...liskCodecUtils.findCodecFromType(tx.type) .toPostable(tx), id: this.identifier(tx), }; }, fromPostable<T = any>(ptx: PostableLiskTransaction<T>): LiskTransaction<T> { return liskCodecUtils.findCodecFromType(ptx.type) .fromPostable(ptx); }, identifier(tx: LiskTransaction<any>) { 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'>; }, }, msgs: { _codec: null, prefix: new Buffer('Lisk Signed Message:\n', 'utf8'), signablePayload(message: Buffer | string) { const msgBuf = Buffer.isBuffer(message) ? message : Buffer.from(message, 'utf8'); const buf = Buffer.concat([ encodeVarInt(this.prefix.length), this.prefix, encodeVarInt(msgBuf.length), msgBuf, ]); return toSha256(toSha256(buf)); }, sign(message: Buffer | string, kp: IKeypair) { return (this._codec as typeof Lisk).raw.sign(this.signablePayload(message), kp); }, verify(message: Buffer | string, signature: Buffer & As<'signature'>, publicKey: Buffer & As<'publicKey'>) { return (this._codec as typeof Lisk).raw.verify(this.signablePayload(message), signature, publicKey); }, }, deriveKeypair(secret: Buffer | string) { const hash = toSha256(Buffer.isBuffer(secret) ? secret : Buffer.from(secret, 'utf8')); const r = ed25519.crypto_sign_seed_keypair(hash); return { privateKey: r.secretKey, publicKey : r.publicKey, }; }, calcAddress(publicKey: (Buffer | string) & As<'publicKey'>) { const hash = toSha256(Buffer.isBuffer(publicKey) ? publicKey : Buffer.from(publicKey, 'hex')); const temp = []; for (let i = 0; i < 8; i++) { temp.push(hash[7 - i]); } return `${Long.fromBytesBE(temp, true).toString()}L` as Address; }, raw: { sign(buf: Buffer, kp: IKeypair) { return ed25519.crypto_sign_detached(buf, kp.privateKey); }, verify(buf: Buffer, signature: Buffer & As<'signature'>, publicKey: Buffer & As<'publicKey'>) { return ed25519.crypto_sign_verify_detached(signature, buf, publicKey); }, }, }; Lisk.msgs._codec = Lisk; Lisk.txs._codec = Lisk;