312 lines
6.1 KiB
JavaScript
312 lines
6.1 KiB
JavaScript
/*!
|
|
* ownership.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 base32 = require('bcrypto/lib/encoding/base32');
|
|
const util = require('bns/lib/util');
|
|
const blake2b = require('bcrypto/lib/blake2b');
|
|
const StubResolver = require('bns/lib/resolver/stub');
|
|
const BNSOwnership = require('bns/lib/ownership');
|
|
const consensus = require('../protocol/consensus');
|
|
const Network = require('../protocol/network');
|
|
const reserved = require('./reserved');
|
|
const {Proof: BNSProof} = BNSOwnership;
|
|
|
|
/** @typedef {import('../types').NetworkType} NetworkType */
|
|
|
|
/*
|
|
* Constants
|
|
*/
|
|
|
|
const EMPTY = Buffer.alloc(0);
|
|
|
|
/** @type {Ownership} */
|
|
let ownership = null;
|
|
|
|
/**
|
|
* Proof
|
|
*/
|
|
|
|
class Proof extends BNSProof {
|
|
constructor() {
|
|
super();
|
|
}
|
|
|
|
/**
|
|
* @param {Buffer} data
|
|
* @returns {this}
|
|
*/
|
|
|
|
decode(data) {
|
|
const br = bio.read(data);
|
|
|
|
if (data.length > 10000)
|
|
throw new Error('Proof too large.');
|
|
|
|
this.read(br);
|
|
|
|
if (br.left() !== 0)
|
|
throw new Error('Trailing data.');
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* @returns {String[]}
|
|
*/
|
|
|
|
getNames() {
|
|
const target = this.getTarget();
|
|
|
|
if (target === '.')
|
|
return ['', target];
|
|
|
|
return [util.label(target, 0), target];
|
|
}
|
|
|
|
/**
|
|
* @returns {String}
|
|
*/
|
|
|
|
getName() {
|
|
return this.getNames()[0];
|
|
}
|
|
|
|
addData(items) {
|
|
return ownership.addData(this, items);
|
|
}
|
|
|
|
getData(network) {
|
|
return ownership.getData(this, network);
|
|
}
|
|
|
|
isWeak() {
|
|
return ownership.isWeak(this);
|
|
}
|
|
|
|
getWindow() {
|
|
return ownership.getWindow(this);
|
|
}
|
|
|
|
isSane() {
|
|
return ownership.isSane(this);
|
|
}
|
|
|
|
verifyTimes(time) {
|
|
return ownership.verifyTimes(this, time);
|
|
}
|
|
|
|
verifySignatures() {
|
|
return ownership.verifySignatures(this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ownership
|
|
*/
|
|
|
|
class Ownership extends BNSOwnership {
|
|
constructor() {
|
|
super();
|
|
|
|
this.Resolver = StubResolver;
|
|
this.secure = true;
|
|
|
|
this.Proof = Proof;
|
|
this.OwnershipProof = Proof;
|
|
}
|
|
|
|
hasPrefix(proof, target, [txt]) {
|
|
// Used only in testing.
|
|
return /^hns-[0-9a-z]+:/.test(txt);
|
|
}
|
|
|
|
isData(proof, target, [txt], network) {
|
|
assert(network && typeof network.claimPrefix === 'string');
|
|
|
|
const prefix = network.claimPrefix;
|
|
|
|
return util.startsWith(txt, prefix);
|
|
}
|
|
|
|
decodeData(txt, network) {
|
|
if (typeof network === 'string')
|
|
network = Network.get(network);
|
|
|
|
assert(network && typeof network.claimPrefix === 'string');
|
|
|
|
const prefix = network.claimPrefix;
|
|
const b32 = txt.substring(prefix.length);
|
|
const raw = base32.decode(b32);
|
|
|
|
const br = bio.read(raw);
|
|
const version = br.readU8();
|
|
const size = br.readU8();
|
|
const hash = br.readBytes(size);
|
|
const fee = br.readVarint();
|
|
const commitHash = br.readHash();
|
|
const commitHeight = br.readU32();
|
|
|
|
return {
|
|
address: {
|
|
version,
|
|
hash: hash.toString('hex')
|
|
},
|
|
fee,
|
|
commitHash: commitHash.toString('hex'),
|
|
commitHeight
|
|
};
|
|
}
|
|
|
|
parseData(proof, target, [txt], network) {
|
|
assert(target !== '.');
|
|
assert(network && typeof network.claimPrefix === 'string');
|
|
|
|
const prefix = network.claimPrefix;
|
|
const b32 = txt.substring(prefix.length);
|
|
const raw = base32.decode(b32);
|
|
|
|
const br = bio.read(raw);
|
|
const version = br.readU8();
|
|
|
|
if (version > 31)
|
|
return null;
|
|
|
|
const size = br.readU8();
|
|
|
|
if (size < 2 || size > 40)
|
|
return null;
|
|
|
|
const hash = br.readBytes(size);
|
|
const fee = br.readVarint();
|
|
|
|
if (fee > consensus.MAX_MONEY)
|
|
return null;
|
|
|
|
const commitHash = br.readHash();
|
|
const commitHeight = br.readU32();
|
|
|
|
br.verifyChecksum(blake2b.digest);
|
|
|
|
if (br.left() !== 0)
|
|
return null;
|
|
|
|
const name = util.label(target, 0);
|
|
const item = reserved.getByName(name);
|
|
|
|
if (!item)
|
|
return null;
|
|
|
|
if (target !== item.target)
|
|
return null;
|
|
|
|
const value = item.value;
|
|
|
|
if (fee > value)
|
|
return null;
|
|
|
|
const [inception, expiration] = proof.getWindow();
|
|
|
|
if (inception === 0 && expiration === 0)
|
|
return null;
|
|
|
|
const weak = proof.isWeak();
|
|
const data = new ProofData();
|
|
|
|
data.name = name;
|
|
data.target = target;
|
|
data.weak = weak;
|
|
data.commitHash = commitHash;
|
|
data.commitHeight = commitHeight;
|
|
data.inception = inception;
|
|
data.expiration = expiration;
|
|
data.fee = fee;
|
|
data.value = value;
|
|
data.version = version;
|
|
data.hash = hash;
|
|
|
|
return data;
|
|
}
|
|
|
|
createData(address, fee, commitHash, commitHeight, network) {
|
|
assert(address && address.hash);
|
|
assert(Number.isSafeInteger(fee) && fee >= 0);
|
|
assert(Buffer.isBuffer(commitHash) && commitHash.length === 32);
|
|
assert((commitHeight >>> 0) === commitHeight);
|
|
assert(commitHeight !== 0);
|
|
assert(network && network.claimPrefix);
|
|
|
|
const prefix = network.claimPrefix;
|
|
const {version, hash} = address;
|
|
|
|
assert(typeof prefix === 'string');
|
|
assert((version & 0xff) === version);
|
|
assert(Buffer.isBuffer(hash));
|
|
assert(version <= 31);
|
|
assert(hash.length >= 2 && hash.length <= 40);
|
|
|
|
const size = 1
|
|
+ 1 + hash.length
|
|
+ bio.sizeVarint(fee)
|
|
+ 32
|
|
+ 4
|
|
+ 4;
|
|
|
|
const bw = bio.write(size);
|
|
|
|
bw.writeU8(version);
|
|
bw.writeU8(hash.length);
|
|
bw.writeBytes(hash);
|
|
bw.writeVarint(fee);
|
|
bw.writeHash(commitHash);
|
|
bw.writeU32(commitHeight);
|
|
bw.writeChecksum(blake2b.digest);
|
|
|
|
const raw = bw.render();
|
|
|
|
return prefix + base32.encode(raw);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ProofData
|
|
*/
|
|
|
|
class ProofData {
|
|
constructor() {
|
|
this.name = '';
|
|
this.target = '.';
|
|
this.weak = false;
|
|
this.commitHash = blake2b.zero;
|
|
this.commitHeight = 0;
|
|
this.inception = 0;
|
|
this.expiration = 0;
|
|
this.fee = 0;
|
|
this.value = 0;
|
|
this.version = 0;
|
|
this.hash = EMPTY;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Ownership
|
|
*/
|
|
|
|
ownership = new Ownership();
|
|
|
|
/*
|
|
* Expose
|
|
*/
|
|
|
|
Ownership.Proof = Proof;
|
|
Ownership.OwnershipProof = Proof;
|
|
Ownership.ProofData = ProofData;
|
|
Ownership.ownership = ownership;
|
|
|
|
module.exports = Ownership;
|