/*!
* claim.js - DNSSEC ownership proofs for hsd
* Copyright (c) 2018, Christopher Jeffrey (MIT License).
* https://github.com/handshake-org/hsd
*/
'use strict';
const assert = require('bsert');
const bio = require('bufio');
const blake2b = require('bcrypto/lib/blake2b');
const consensus = require('../protocol/consensus');
const policy = require('../protocol/policy');
const rules = require('../covenants/rules');
const Ownership = require('../covenants/ownership');
const InvItem = require('./invitem');
const TX = require('./tx');
const Input = require('./input');
const Output = require('./output');
const {OwnershipProof} = Ownership;
/** @typedef {import('../types').Hash} Hash */
/** @typedef {import('../types').Amount} AmountValue */
/** @typedef {import('../types').Rate} Rate */
/** @typedef {import('../types').BufioWriter} BufioWriter */
/** @typedef {import('../protocol/network')} Network */
/*
* Constants
*/
const EMPTY = Buffer.alloc(0);
/**
* Claim
* @extends {bio.Struct}
*/
class Claim extends bio.Struct {
constructor() {
super();
this.blob = EMPTY;
/** @type {Hash?} */
this._hash = null;
this._data = null;
}
/**
* @returns {this}
*/
refresh() {
this._hash = null;
this._data = null;
return this;
}
/**
* @returns {Hash}
*/
hash() {
if (!this._hash)
this._hash = blake2b.digest(this.blob);
return this._hash;
}
/**
* @returns {String}
*/
hashHex() {
return this.hash().toString('hex');
}
/**
* @param {Network} network
* @returns {Object}
*/
getData(network) {
if (!this._data) {
const proof = this.getProof();
if (!proof)
return null;
const data = proof.getData(network);
if (!data)
return null;
this._data = data;
}
return this._data;
}
/**
* @returns {Number}
*/
getSize() {
return 2 + this.blob.length;
}
/**
* @param {BufioWriter} bw
* @returns {BufioWriter}
*/
write(bw) {
bw.writeU16(this.blob.length);
bw.writeBytes(this.blob);
return bw;
}
/**
* @param {Buffer} data
* @returns {this}
*/
decode(data) {
const br = bio.read(data);
if (data.length > 2 + 10000)
throw new Error('Proof too large.');
this.read(br);
if (br.left() !== 0)
throw new Error('Trailing data.');
return this;
}
/**
* @param {bio.BufferReader} br
* @returns {this}
*/
read(br) {
const size = br.readU16();
if (size > 10000)
throw new Error('Invalid claim size.');
this.blob = br.readBytes(size);
return this;
}
/**
* @returns {InvItem}
*/
toInv() {
return new InvItem(InvItem.types.CLAIM, this.hash());
}
/**
* @returns {Number}
*/
getWeight() {
return this.getSize();
}
/**
* @returns {Number}
*/
getVirtualSize() {
const scale = consensus.WITNESS_SCALE_FACTOR;
return (this.getWeight() + scale - 1) / scale | 0;
}
/**
* @param {Number} [size]
* @param {Number} [rate]
* @returns {AmountValue}
*/
getMinFee(size, rate) {
if (size == null)
size = this.getVirtualSize();
return policy.getMinFee(size, rate);
}
/**
* @param {Network} [network]
* @returns {AmountValue}
*/
getFee(network) {
const data = this.getData(network);
assert(data);
return data.fee;
}
/**
* @param {Number} [size]
* @param {Network} [network]
* @returns {Rate}
*/
getRate(size, network) {
const fee = this.getFee(network);
if (size == null)
size = this.getVirtualSize();
return policy.getRate(size, fee);
}
/**
* @param {Network} network
* @param {Number} height
* @returns {TX}
*/
toTX(network, height) {
const data = this.getData(network);
assert(data);
const tx = new TX();
tx.inputs.push(new Input());
tx.outputs.push(new Output());
const input = new Input();
input.witness.items.push(this.blob);
const output = new Output();
output.value = data.value - data.fee;
output.address.version = data.version;
output.address.hash = data.hash;
let flags = 0;
if (data.weak)
flags |= 1;
output.covenant.setClaim(
rules.hashName(data.name),
height,
Buffer.from(data.name, 'binary'),
flags,
data.commitHash,
data.commitHeight
);
tx.inputs.push(input);
tx.outputs.push(output);
tx.refresh();
return tx;
}
/**
* @returns {OwnershipProof}
*/
getProof() {
try {
return this.toProof();
} catch (e) {
return new OwnershipProof();
}
}
/**
* @returns {OwnershipProof}
*/
toProof() {
return OwnershipProof.decode(this.blob);
}
/**
* @returns {Buffer}
*/
toBlob() {
return this.blob;
}
/**
* @returns {Object}
*/
getJSON() {
const proof = this.getProof();
return proof.toJSON();
}
/**
* Inject properties from blob.
* @param {Buffer} blob
* @returns {this}
*/
fromBlob(blob) {
assert(Buffer.isBuffer(blob));
this.blob = blob;
return this;
}
/**
* @param {OwnershipProof} proof
* @returns {this}
*/
fromProof(proof) {
assert(proof instanceof OwnershipProof);
this.blob = proof.encode();
return this;
}
/**
* Instantiate claim from raw proof.
* @param {Buffer} blob
* @returns {Claim}
*/
static fromBlob(blob) {
return new this().fromBlob(blob);
}
/**
* Instantiate claim from proof.
* @param {OwnershipProof} proof
* @returns {Claim}
*/
static fromProof(proof) {
return new this().fromProof(proof);
}
}
/*
* Expose
*/
module.exports = Claim;