483 lines
9.8 KiB
JavaScript
483 lines
9.8 KiB
JavaScript
/* eslint camelcase: 'off' */
|
|
|
|
'use strict';
|
|
|
|
const assert = require('bsert');
|
|
const bio = require('bufio');
|
|
const base16 = require('bcrypto/lib/encoding/base16');
|
|
const bech32 = require('bcrypto/lib/encoding/bech32');
|
|
const BLAKE2b = require('bcrypto/lib/blake2b');
|
|
const SHA256 = require('bcrypto/lib/sha256');
|
|
const rsa = require('bcrypto/lib/rsa');
|
|
const p256 = require('bcrypto/lib/p256');
|
|
const ed25519 = require('bcrypto/lib/ed25519');
|
|
const {countLeft} = require('bcrypto/lib/encoding/util');
|
|
const Goo = require('goosig');
|
|
|
|
/** @typedef {import('../types').Hash} Hash */
|
|
/** @typedef {import('../types').Amount} AmountValue */
|
|
/** @typedef {import('../types').BufioWriter} BufioWriter */
|
|
|
|
/*
|
|
* Goo
|
|
*/
|
|
|
|
const goo = new Goo(Goo.RSA2048, 2, 3);
|
|
|
|
/*
|
|
* Constants
|
|
*/
|
|
|
|
const keyTypes = {
|
|
RSA: 0,
|
|
GOO: 1,
|
|
P256: 2,
|
|
ED25519: 3,
|
|
ADDRESS: 4
|
|
};
|
|
|
|
const keyTypesByVal = {
|
|
[keyTypes.RSA]: 'RSA',
|
|
[keyTypes.GOO]: 'GOO',
|
|
[keyTypes.P256]: 'P256',
|
|
[keyTypes.ED25519]: 'ED25519',
|
|
[keyTypes.ADDRESS]: 'ADDRESS'
|
|
};
|
|
|
|
const EMPTY = Buffer.alloc(0);
|
|
|
|
/**
|
|
* AirdropKey
|
|
*/
|
|
|
|
class AirdropKey extends bio.Struct {
|
|
constructor() {
|
|
super();
|
|
this.type = keyTypes.RSA;
|
|
this.n = EMPTY;
|
|
this.e = EMPTY;
|
|
this.C1 = EMPTY;
|
|
this.point = EMPTY;
|
|
this.version = 0;
|
|
this.address = EMPTY;
|
|
this.value = 0;
|
|
this.sponsor = false;
|
|
this.nonce = SHA256.zero;
|
|
this.tweak = null;
|
|
}
|
|
|
|
/**
|
|
* @param {AirdropKey} key
|
|
* @returns {this}
|
|
*/
|
|
|
|
inject(key) {
|
|
assert(key instanceof AirdropKey);
|
|
|
|
this.type = key.type;
|
|
this.n = key.n;
|
|
this.e = key.e;
|
|
this.C1 = key.C1;
|
|
this.point = key.point;
|
|
this.version = key.version;
|
|
this.address = key.address;
|
|
this.value = key.value;
|
|
this.sponsor = key.sponsor;
|
|
this.nonce = key.nonce;
|
|
this.tweak = key.tweak;
|
|
|
|
return this;
|
|
}
|
|
|
|
isRSA() {
|
|
return this.type === keyTypes.RSA;
|
|
}
|
|
|
|
isGoo() {
|
|
return this.type === keyTypes.GOO;
|
|
}
|
|
|
|
isP256() {
|
|
return this.type === keyTypes.P256;
|
|
}
|
|
|
|
isED25519() {
|
|
return this.type === keyTypes.ED25519;
|
|
}
|
|
|
|
isAddress() {
|
|
return this.type === keyTypes.ADDRESS;
|
|
}
|
|
|
|
isWeak() {
|
|
if (!this.isRSA())
|
|
return false;
|
|
|
|
return countLeft(this.n) < 2048 - 7;
|
|
}
|
|
|
|
/**
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
validate() {
|
|
switch (this.type) {
|
|
case keyTypes.RSA: {
|
|
let key;
|
|
|
|
try {
|
|
key = rsa.publicKeyImport({ n: this.n, e: this.e });
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
|
|
const bits = rsa.publicKeyBits(key);
|
|
|
|
// Allow 1024 bit RSA for now.
|
|
// We can softfork out later.
|
|
return bits >= 1024 && bits <= 4096;
|
|
}
|
|
|
|
case keyTypes.GOO: {
|
|
return this.C1.length === goo.size;
|
|
}
|
|
|
|
case keyTypes.P256: {
|
|
return p256.publicKeyVerify(this.point);
|
|
}
|
|
|
|
case keyTypes.ED25519: {
|
|
return ed25519.publicKeyVerify(this.point);
|
|
}
|
|
|
|
case keyTypes.ADDRESS: {
|
|
return true;
|
|
}
|
|
|
|
default: {
|
|
throw new assert.AssertionError('Invalid key type.');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Buffer} msg
|
|
* @param {Buffer} sig
|
|
* @returns {Boolean}
|
|
*/
|
|
|
|
verify(msg, sig) {
|
|
assert(Buffer.isBuffer(msg));
|
|
assert(Buffer.isBuffer(sig));
|
|
|
|
switch (this.type) {
|
|
case keyTypes.RSA: {
|
|
let key;
|
|
|
|
try {
|
|
key = rsa.publicKeyImport({ n: this.n, e: this.e });
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
|
|
return rsa.verify(SHA256, msg, sig, key);
|
|
}
|
|
|
|
case keyTypes.GOO: {
|
|
return goo.verify(msg, sig, this.C1);
|
|
}
|
|
|
|
case keyTypes.P256: {
|
|
return p256.verify(msg, sig, this.point);
|
|
}
|
|
|
|
case keyTypes.ED25519: {
|
|
return ed25519.verify(msg, sig, this.point);
|
|
}
|
|
|
|
case keyTypes.ADDRESS: {
|
|
return true;
|
|
}
|
|
|
|
default: {
|
|
throw new assert.AssertionError('Invalid key type.');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @returns {Hash}
|
|
*/
|
|
|
|
hash() {
|
|
const bw = bio.pool(this.getSize());
|
|
this.write(bw);
|
|
return BLAKE2b.digest(bw.render());
|
|
}
|
|
|
|
getSize() {
|
|
let size = 0;
|
|
|
|
size += 1;
|
|
|
|
switch (this.type) {
|
|
case keyTypes.RSA:
|
|
assert(this.n.length <= 0xffff);
|
|
assert(this.e.length <= 0xff);
|
|
size += 2;
|
|
size += this.n.length;
|
|
size += 1;
|
|
size += this.e.length;
|
|
size += 32;
|
|
break;
|
|
case keyTypes.GOO:
|
|
size += goo.size;
|
|
break;
|
|
case keyTypes.P256:
|
|
size += 33;
|
|
size += 32;
|
|
break;
|
|
case keyTypes.ED25519:
|
|
size += 32;
|
|
size += 32;
|
|
break;
|
|
case keyTypes.ADDRESS:
|
|
size += 1;
|
|
size += 1;
|
|
size += this.address.length;
|
|
size += 8;
|
|
size += 1;
|
|
break;
|
|
default:
|
|
throw new assert.AssertionError('Invalid key type.');
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
/**
|
|
* @param {BufioWriter} bw
|
|
* @returns {BufioWriter}
|
|
*/
|
|
|
|
write(bw) {
|
|
bw.writeU8(this.type);
|
|
|
|
switch (this.type) {
|
|
case keyTypes.RSA:
|
|
bw.writeU16(this.n.length);
|
|
bw.writeBytes(this.n);
|
|
bw.writeU8(this.e.length);
|
|
bw.writeBytes(this.e);
|
|
bw.writeBytes(this.nonce);
|
|
break;
|
|
case keyTypes.GOO:
|
|
bw.writeBytes(this.C1);
|
|
break;
|
|
case keyTypes.P256:
|
|
case keyTypes.ED25519:
|
|
bw.writeBytes(this.point);
|
|
bw.writeBytes(this.nonce);
|
|
break;
|
|
case keyTypes.ADDRESS:
|
|
bw.writeU8(this.version);
|
|
bw.writeU8(this.address.length);
|
|
bw.writeBytes(this.address);
|
|
bw.writeU64(this.value);
|
|
bw.writeU8(this.sponsor ? 1 : 0);
|
|
break;
|
|
default:
|
|
throw new assert.AssertionError('Invalid key type.');
|
|
}
|
|
|
|
return bw;
|
|
}
|
|
|
|
/**
|
|
* @param {bio.BufferReader} br
|
|
* @returns {this}
|
|
*/
|
|
|
|
read(br) {
|
|
this.type = br.readU8();
|
|
|
|
switch (this.type) {
|
|
case keyTypes.RSA: {
|
|
this.n = br.readBytes(br.readU16());
|
|
this.e = br.readBytes(br.readU8());
|
|
this.nonce = br.readBytes(32);
|
|
break;
|
|
}
|
|
|
|
case keyTypes.GOO: {
|
|
this.C1 = br.readBytes(goo.size);
|
|
break;
|
|
}
|
|
|
|
case keyTypes.P256: {
|
|
this.point = br.readBytes(33);
|
|
this.nonce = br.readBytes(32);
|
|
break;
|
|
}
|
|
|
|
case keyTypes.ED25519: {
|
|
this.point = br.readBytes(32);
|
|
this.nonce = br.readBytes(32);
|
|
break;
|
|
}
|
|
|
|
case keyTypes.ADDRESS: {
|
|
this.version = br.readU8();
|
|
this.address = br.readBytes(br.readU8());
|
|
this.value = br.readU64();
|
|
this.sponsor = br.readU8() === 1;
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
throw new Error('Unknown key type.');
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* @param {String} addr
|
|
* @param {AmountValue} value
|
|
* @param {Boolean} sponsor
|
|
* @returns {this}
|
|
*/
|
|
|
|
fromAddress(addr, value, sponsor = false) {
|
|
assert(typeof addr === 'string');
|
|
assert(Number.isSafeInteger(value) && value >= 0);
|
|
assert(typeof sponsor === 'boolean');
|
|
|
|
const [hrp, version, hash] = bech32.decode(addr);
|
|
|
|
assert(hrp === 'hs' || hrp === 'ts' || hrp === 'rs');
|
|
assert(version === 0);
|
|
assert(hash.length === 20 || hash.length === 32);
|
|
|
|
this.type = keyTypes.ADDRESS;
|
|
this.version = version;
|
|
this.address = hash;
|
|
this.value = value;
|
|
this.sponsor = sponsor;
|
|
|
|
return this;
|
|
}
|
|
|
|
getJSON() {
|
|
return {
|
|
type: keyTypesByVal[this.type] || 'UNKNOWN',
|
|
n: this.n.length > 0
|
|
? this.n.toString('hex')
|
|
: undefined,
|
|
e: this.e.length > 0
|
|
? this.e.toString('hex')
|
|
: undefined,
|
|
C1: this.C1.length > 0
|
|
? this.C1.toString('hex')
|
|
: undefined,
|
|
point: this.point.length > 0
|
|
? this.point.toString('hex')
|
|
: undefined,
|
|
version: this.address.length > 0
|
|
? this.version
|
|
: undefined,
|
|
address: this.address.length > 0
|
|
? this.address.toString('hex')
|
|
: undefined,
|
|
value: this.value || undefined,
|
|
sponsor: this.value
|
|
? this.sponsor
|
|
: undefined,
|
|
nonce: !this.isGoo() && !this.isAddress()
|
|
? this.nonce.toString('hex')
|
|
: undefined
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {Object} json
|
|
* @returns {this}
|
|
*/
|
|
|
|
fromJSON(json) {
|
|
assert(json && typeof json === 'object');
|
|
assert(typeof json.type === 'string');
|
|
assert(Object.prototype.hasOwnProperty.call(keyTypes, json.type));
|
|
|
|
this.type = keyTypes[json.type];
|
|
|
|
console.log(base16.decode.toString());
|
|
switch (this.type) {
|
|
case keyTypes.RSA: {
|
|
this.n = base16.decode(json.n);
|
|
this.e = base16.decode(json.e);
|
|
this.nonce = base16.decode(json.nonce);
|
|
break;
|
|
}
|
|
|
|
case keyTypes.GOO: {
|
|
this.C1 = base16.decode(json.C1);
|
|
break;
|
|
}
|
|
|
|
case keyTypes.P256: {
|
|
this.point = base16.decode(json.point);
|
|
this.nonce = base16.decode(json.nonce);
|
|
break;
|
|
}
|
|
|
|
case keyTypes.ED25519: {
|
|
this.point = base16.decode(json.point);
|
|
this.nonce = base16.decode(json.nonce);
|
|
break;
|
|
}
|
|
|
|
case keyTypes.ADDRESS: {
|
|
assert((json.version & 0xff) === json.version);
|
|
assert(Number.isSafeInteger(json.value) && json.value >= 0);
|
|
assert(typeof json.sponsor === 'boolean');
|
|
this.version = json.version;
|
|
this.address = base16.decode(json.address);
|
|
this.value = json.value;
|
|
this.sponsor = json.sponsor;
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
throw new Error('Unknown key type.');
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* @param {String} addr
|
|
* @param {AmountValue} value
|
|
* @param {Boolean} sponsor
|
|
* @returns {AirdropKey}
|
|
*/
|
|
|
|
static fromAddress(addr, value, sponsor) {
|
|
return new this().fromAddress(addr, value, sponsor);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Static
|
|
*/
|
|
|
|
AirdropKey.keyTypes = keyTypes;
|
|
AirdropKey.keyTypesByVal = keyTypesByVal;
|
|
|
|
/*
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = AirdropKey;
|