@ducatus/ducatus-wallet-client-rev
Version:
Client for @ducatus/ducatus-wallet-service-rev
278 lines (240 loc) • 7.12 kB
text/typescript
'use strict';
import { BitcoreLib } from '../../../ducatus-crypto-wallet-core-rev';
import { Constants, Utils } from './common';
const $ = require('preconditions').singleton();
const _ = require('lodash');
const Bitcore = BitcoreLib;
const sjcl = require('sjcl');
export class Credentials {
static FIELDS = [
'coin',
'network',
'xPrivKey', // obsolete
'xPrivKeyEncrypted', // obsolte
'xPubKey',
'requestPrivKey',
'requestPubKey',
'copayerId',
'publicKeyRing',
'walletId',
'walletName',
'm',
'n',
'walletPrivKey',
'personalEncryptingKey',
'sharedEncryptingKey',
'copayerName',
'externalSource',
'mnemonic', // Obsolete
'mnemonicEncrypted', // Obsolete
'entropySource',
'mnemonicHasPassphrase',
'derivationStrategy',
'account',
'compliantDerivation',
'addressType',
'hwInfo', // Obsolete
'entropySourcePath', // Obsolete
'use145forBCH', // Obsolete
'version',
'rootPath', // this is only for information
'keyId', // this is only for information
'token' // is this wallet is for a ERC20 token
];
version: number;
account: number;
walletPrivKey: any;
sharedEncryptingKey: any;
walletId: any;
walletName: any;
m: any;
n: any;
copayerName: any;
xPubKey: any;
requestPubKey: any;
publicKeyRing: any;
rootPath: any;
derivationStrategy: any;
network: string;
coin: string;
use145forBCH: any;
addressType: string;
keyId: string;
token?: string;
externalSource?: boolean; // deprecated property?
constructor() {
this.version = 2;
this.account = 0;
}
/*
*coin, xPrivKey, account, network
*/
static fromDerivedKey(opts) {
$.shouldBeString(opts.coin);
$.shouldBeString(opts.network);
$.shouldBeNumber(opts.account, 'Invalid account');
$.shouldBeString(opts.xPubKey, 'Invalid xPubKey');
$.shouldBeString(opts.rootPath, 'Invalid rootPath');
$.shouldBeString(opts.keyId, 'Invalid keyId');
$.shouldBeString(opts.requestPrivKey, 'Invalid requestPrivKey');
$.checkArgument(_.isUndefined(opts.nonCompliantDerivation));
opts = opts || {};
var x: any = new Credentials();
x.coin = opts.coin;
x.network = opts.network;
x.account = opts.account;
x.n = opts.n;
x.xPubKey = opts.xPubKey;
x.keyId = opts.keyId;
// this allows to set P2SH in old n=1 wallets
if (_.isUndefined(opts.addressType)) {
x.addressType = opts.n == 1 ? Constants.SCRIPT_TYPES.P2PKH : Constants.SCRIPT_TYPES.P2SH;
} else {
x.addressType = opts.addressType;
}
// Only used for info
x.rootPath = opts.rootPath;
if (opts.walletPrivKey) {
x.addWalletPrivateKey(opts.walletPrivKey);
}
x.requestPrivKey = opts.requestPrivKey;
const priv = Bitcore.PrivateKey(x.requestPrivKey);
x.requestPubKey = priv.toPublicKey().toString();
const prefix = 'personalKey';
const entropySource = Bitcore.crypto.Hash.sha256(priv.toBuffer()).toString('hex');
const b = Buffer.from(entropySource, 'hex');
const b2 = Bitcore.crypto.Hash.sha256hmac(b, Buffer.from(prefix));
x.personalEncryptingKey = b2.slice(0, 16).toString('base64');
x.copayerId = Utils.xPubToCopayerId(x.coin, x.xPubKey);
x.publicKeyRing = [
{
xPubKey: x.xPubKey,
requestPubKey: x.requestPubKey
}
];
return x;
}
/*
* creates an ERC20 wallet from a ETH wallet
*/
getTokenCredentials(token: { name: string; symbol: string; address: string }) {
const ret = _.cloneDeep(this);
ret.walletId = `${ret.walletId}-${token.address}`;
ret.coin = token.symbol.toLowerCase();
ret.walletName = token.name;
ret.token = token;
return ret;
}
getRootPath() {
// This is for OLD v1.0 credentials only.
var legacyRootPath = () => {
// legacy base path schema
var purpose;
switch (this.derivationStrategy) {
case Constants.DERIVATION_STRATEGIES.BIP45:
return "m/45'";
case Constants.DERIVATION_STRATEGIES.BIP44:
purpose = '44';
break;
case Constants.DERIVATION_STRATEGIES.BIP48:
purpose = '48';
break;
}
var coin = '0';
if (this.network != 'livenet' && Constants.UTXO_COINS.includes(this.coin)) {
coin = '1';
} else if (this.coin == 'bch') {
if (this.use145forBCH) {
coin = '145';
} else {
coin = '1025';
}
} else if (this.coin == 'btc') {
coin = '1025';
} else if (this.coin == 'duc') {
coin = '0';
} else if (this.coin == 'eth') {
coin = '60';
} else if (this.coin == 'xrp') {
coin = '144';
} else if (this.coin == 'ducx') {
coin = '1060';
} else {
throw new Error('unknown coin: ' + this.coin);
}
return 'm/' + purpose + "'/" + coin + "'/" + this.account + "'";
};
if (!this.rootPath) {
this.rootPath = legacyRootPath();
}
return this.rootPath;
}
static fromObj(obj) {
var x: any = new Credentials();
if (!obj.version || obj.version < x.version) {
throw new Error('Obsolete credentials version');
}
if (obj.version != x.version) {
throw new Error('Bad credentials version');
}
_.each(Credentials.FIELDS, function(k) {
x[k] = obj[k];
});
if (x.externalSource) {
throw new Error('External Wallets are no longer supported');
}
x.coin = x.coin || 'btc';
x.addressType = x.addressType || Constants.SCRIPT_TYPES.P2SH;
x.account = x.account || 0;
$.checkState(x.xPrivKey || x.xPubKey || x.xPrivKeyEncrypted, 'invalid input');
return x;
}
toObj() {
var self = this;
var x = {};
_.each(Credentials.FIELDS, function(k) {
x[k] = self[k];
});
return x;
}
addWalletPrivateKey(walletPrivKey) {
this.walletPrivKey = walletPrivKey;
this.sharedEncryptingKey = Utils.privateKeyToAESKey(walletPrivKey);
}
addWalletInfo(walletId, walletName, m, n, copayerName, opts) {
opts = opts || {};
this.walletId = walletId;
this.walletName = walletName;
this.m = m;
if (opts.useNativeSegwit) {
this.addressType = n == 1 ? Constants.SCRIPT_TYPES.P2WPKH : Constants.SCRIPT_TYPES.P2WSH;
}
if (this.n != n && !opts.allowOverwrite) {
// we always allow multisig n overwrite
if (this.n == 1 || n == 1) {
throw new Error(`Bad nr of copayers in addWalletInfo: this: ${this.n} got: ${n}`);
}
}
this.n = n;
if (copayerName) this.copayerName = copayerName;
if (n == 1) {
this.addPublicKeyRing([
{
xPubKey: this.xPubKey,
requestPubKey: this.requestPubKey
}
]);
}
}
hasWalletInfo() {
return !!this.walletId;
}
addPublicKeyRing(publicKeyRing) {
this.publicKeyRing = _.clone(publicKeyRing);
}
isComplete() {
if (!this.m || !this.n) return false;
if (!this.publicKeyRing || this.publicKeyRing.length != this.n) return false;
return true;
}
}