import { verify } from 'bitcoinjs-message'
/**
* An ES6 Multipart Single Class
* @class
*/
class MPSingle {
/**
* Construct a Multipart Single by passing in an object or a valid JSON string
* ##### Examples
* ```javascript
* let myOIPObject = {
* part: 0,
* max: 1,
* reference: `${firstTXIDRef}`,
* address: `${p2pkh}`,
* signature: `${signature}`,
* data: `${data}`
* }
* let mps = new MPSingle(myOIPObject)
* //or
* mps = new MPSingle(JSON.stringify(myOIPObject))
* ```
* @param {String|Object} input - a multipart chunk
* @return {MPSingle}
*/
constructor (input) {
// this._source = input
this.prefix = 'oip-mp'
this.part = undefined
this.max = undefined
this.address = undefined
this.reference = undefined
this.signature = undefined
this.data = undefined
this.meta = {
complete: undefined,
stale: undefined,
time: undefined,
txid: undefined,
block: undefined,
block_hash: undefined, // eslint-disable-line
assembled: undefined,
tx: undefined
}
this.fromInput(input)
}
/**
* Get Part Number
* @return {number}
*/
getPart () {
return parseInt(this.part)
}
/**
* Set part number
* @param {number} part
*/
setPart (part) {
this.part = part
}
/**
* Get max number of parts
* @return {number}
*/
getMax () {
return parseInt(this.max)
}
/**
* Set max number of parts
* @param {number} max
*/
setMax (max) {
this.max = max
}
/**
* Get publisher address
* @return {string}
*/
getAddress () {
return this.address
}
/**
* Set Publisher address
* @param {string} address
*/
setAddress (address) {
this.address = address
}
/**
* Get the reference to the first part's TXID
* @return {string}
*/
getReference () {
return this.reference
}
/**
* Set the reference to the first part's TXID
* @param {string} reference
*/
setReference (reference) {
this.reference = reference
}
/**
* Get signature
* @return {string}
*/
getSignature () {
return this.signature
}
/**
* Set signature
* @param {string} signature
*/
setSignature (signature) {
this.signature = signature
}
/**
* Get multipart data
* @return {string}
*/
getData () {
return this.data
}
/**
* Set multipart data
* @param {string} data - floData
*/
setData (data) {
this.data = data
}
/**
* Get the multipart meta data
* @return {Object}
*/
getMeta () {
return this.meta
}
/**
* Check if multipart is complete
* @return {Boolean}
*/
isComplete () {
return this.meta.complete
}
/**
* Set whether mulitpart is complete
* @param {boolean} isComplete
*/
setIsComplete (isComplete) {
this.meta.complete = isComplete
}
/**
* Check if multipart is stale
* @return {Boolean}
*/
isStale () {
return this.meta.stale
}
/**
* Set stale param
* @param {Boolean} isStale
*/
setIsStale (isStale) {
this.meta.stale = isStale
}
getTime () {
return this.meta.time
}
setTime (time) {
this.meta.time = time
}
/**
* Get TXID
* @return {string}
*/
getTXID () {
return this.meta.txid
}
/**
* Set TXID
* @param {string} txid
*/
setTXID (txid) {
this.meta.txid = txid
}
/**
* Get Block Height
* @return {number}
*/
getBlock () {
return parseInt(this.meta.block)
}
/**
* Set Block Height
* @param {number|string} block
*/
setBlock (block) {
this.meta.block = block
}
/**
* Get block hash
* @return {string}
*/
getBlockHash () {
return this.meta.block_hash // eslint-disable-line
}
/**
* Set block hash
* @param {string} blockHash
*/
setBlockHash (blockHash) {
this.meta.block_hash = blockHash // eslint-disable-line
}
/**
* Get assembled multipart
* @return {string}
*/
getAssembled () {
return this.meta.assembled
}
/**
* Set assembled multipart
* @param {string} assembled - assembled multipart
*/
setAssembled (assembled) {
this.meta.assembled = assembled
}
/**
* Get Transaction
* @return {Object}
* @deprecated
*/
// getTX() {
// return this.meta.tx
// }
/**
* Set Transaction
* @param {Object} tx
* @deprecated
*/
// setTX(tx) {
// this.meta.tx = tx
// }
/**
* Get original source data
* @private
* @deprecated
*/
// _getSource() {
// return this._source
// }
/**
* Construct a MPSingle from a JSON string or Object
* @param {string|object} input - see constructor example
* @return {null}
*/
fromInput (input) {
if (!input) { return new Error(`No input!`) }
if (typeof input === 'string') {
try {
input = JSON.parse(input)
} catch (err) {
return new Error(`Input is invalid JSON: ${err}`)
}
}
if (input.part !== undefined) {
this.setPart(input.part)
}
if (input.max !== undefined) {
this.setMax(input.max)
}
if (input.address) {
this.setAddress(input.address)
}
if (input.reference) {
this.setReference(input.reference)
}
if (input.signature) {
this.setSignature(input.signature)
}
if (input.data) {
this.setData(input.data)
}
if (!input.meta) return
if (input.meta.complete !== undefined) {
this.setIsComplete(input.meta.complete)
this.setAssembled(input.meta.assembled)
}
if (input.meta.stale !== undefined) {
this.setIsStale(input.meta.stale)
}
if (input.meta.time) {
this.setTime(input.meta.time)
}
if (input.meta.txid) {
this.setTXID(input.meta.txid)
}
if (input.meta.block) {
this.setBlock(input.meta.block)
}
if (input.meta.block_hash) {
this.setBlockHash(input.meta.block_hash)
}
// if (input.meta.tx) {
// this.setTX(input.meta.tx)
// }
}
isValid () {
if (this.getPart() < 0 || this.getPart() === undefined || this.getPart() === '') {
return { success: false, error: "Part number can't be negative, null, or undefined" }
}
if (this.getPart() > this.getMax()) {
return { success: false, error: 'Part number too high for total parts!' }
}
if (this.getMax() < 1) {
return { success: false, error: 'Must have more than one part to be a MULTIPART message!' }
}
if (!this.getAddress()) {
return { success: false, error: 'Must have a Publisher Address!' }
}
if (!this.getReference() && this.getPart() !== 0) {
return {
success: false,
error: 'Only the first part in a multipart message can have a blank first part TXID!'
}
}
if (isNaN(this.getPart()) || isNaN(this.getPart())) {
return { success: false, error: 'The part number and the total part number must be of NUMBER types' }
}
if (!this.getSignature()) {
return { success: false, error: 'Must have a Signature!' }
}
if (!this.hasValidSignature()) {
return { success: false, error: 'Invalid Signature' }
}
this.is_valid = true
return { success: true }
}
/**
* Convert MPSingle to string
* @return {string}
* @example
* ```
* oip-mp(4,6,FLZXRaHzVPxJJfaoM32CWT4GZHuj2rx63k,8c204c5f39,H9dqFw5Pd//qwHeEQA+ENifGvvs/0X1sLUXLQKj2L5qdI/BIJMBX2w3TKETHeNg3MMhA1i3PYVT2FnC8y/BxvUM=):":"Single Track","duration":268},{"fname":"miltjordan-vanishingbreed.jpg","fsize":40451,"type":"Image","subtype":"album-art"},{"fname":"miltjordan-angelsgettheblues.jpg","fsize":54648,"type":"Image","subtype":"cover"}],"location":"QmWmth4ES4ZH9Wgz6Z7S7dRFF8MzJVGgDhit5KzH5uCvZz"},"payment":{"fiat":"USD","scale":"1000:1","maxdisc":30,"promoter":15,"retailer":15,"sugTip"
* ```
*/
toString () {
if (this.isValid().success) {
let part = this.getPart()
let max = this.getMax()
let addr = this.getAddress()
// if part === 0, return empty string
let ref = part ? this.getReference() : ''
let sig = this.getSignature()
let data = this.getData()
return `${this.prefix}(${part},${max},${addr},${ref},${sig}):${data}`
} else return new Error(`Invalid multipart: ${this.isValid().error}`)
}
/**
* Get Signature Data (preimage: the message parameter for the signing function)
* @return {string} signatureData
*/
getSignatureData () {
let part = this.getPart()
let max = this.getMax()
let address = this.getAddress()
let reference = this.getPart() ? this.getReference() : ''
let data = this.getData()
return `${part}-${max}-${address}-${reference}-${data}`
}
/**
* Get the signature of a specific message that can be verified by others
* @param {Object} ECPair - Elliptic Curve Key Pair (bitcoinjs-lib/ecpair)
* @return {Object} success - Returns a success object
* ```javascript
* {success: true, signature: 'base64', error: undefined}
*
* //or something like
*
* {success: false, signature: undefined, error: "Missing address for signature}
*```
*
* ```javascript
* //nice little trick
* let {success, signature, error} = mp.signSelf(ECPair)
*
* if (success) {
* mp.setSignature(signature)
* } else {
* handle(error)
* }
* ```
*/
async signSelf (signMessage) {
if (!signMessage) { return { success: false, error: 'No signMessage function available! Unable to sign message!' } }
if (this.getPart() === undefined) {
return { success: false, error: 'Missing part number! Unable to sign message!' }
}
if (this.getMax() === undefined) {
return { success: false, error: 'Missing maximum part number! Unable to sign message!' }
}
// if the first txid is not set on a part greater than 0, throw
// part 0 multiparts should have an undefined ref
if (this.getReference() === undefined && this.getPart() !== 0) {
return { success: false, error: 'Missing first txid reference! Unable to sign message!' }
}
if (this.getData() === undefined) {
return { success: false, error: 'Missing data! Unable to sign message!' }
}
let signature
try {
signature = await signMessage(this.getSignatureData())
} catch (e) {
return { success: false, error: e }
}
this.setSignature(signature)
return { success: true, signature }
}
/**
* Check to see if the signature is valid
* @param {string} [message_prefix=\u001bFlorincoin Signed Message:]
* @return {boolean}
* @example
* if (multipart.hasValidSignature()) {
* launchSpaceship()
* }
*/
hasValidSignature (message_prefix = '\u001bFlorincoin Signed Message:\n') {
return verify(this.getSignatureData(), this.getAddress(), this.getSignature(), message_prefix)
}
}
export default MPSingle