Skip to content

Commit b912cf7

Browse files
authored
Merge pull request #34 from BitGo/support-key-signatures
[BG-9016] added support for key signatures
2 parents b5bd848 + 6a7a15f commit b912cf7

File tree

7 files changed

+182
-41
lines changed

7 files changed

+182
-41
lines changed

app/admin.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const MasterKey = require('./models/masterkey.js');
1313
const signingTool = require('./sign.js');
1414
const WalletKey = require('./models/walletkey.js');
1515
const utils = require('./utils');
16+
const bitcoinMessage = require('bitcoinjs-message');
1617

1718
const parser = new ArgumentParser({
1819
version: pjson.version,
@@ -196,6 +197,28 @@ const validateKey = function(key, type) {
196197
return false;
197198
}
198199

200+
// if we have provided a verificationPub, then check the key signature
201+
if (process.config.verificationPub) {
202+
203+
if (!key.signature) {
204+
console.log(`Key ${key.pub} requires a signature and does not have one.`);
205+
return false;
206+
}
207+
208+
let validSig = false;
209+
try {
210+
validSig = bitcoinMessage.verify(key.pub, process.config.verificationPub, key.signature);
211+
} catch (err) {
212+
console.log(`There was an error when verifying key ${key.pub}: ` + err.message);
213+
return false;
214+
}
215+
216+
if (!validSig) {
217+
console.log(`Invalid signature detected on key ${key.pub}`);
218+
return false;
219+
}
220+
}
221+
199222
return true;
200223
};
201224

@@ -216,6 +239,7 @@ const saveKeys = co(function *(keys, type) {
216239
type: type,
217240
pub: key.pub,
218241
path: key.path,
242+
signature: key.signature,
219243
keyCount: 0
220244
}));
221245

@@ -372,4 +396,4 @@ const run = co(function *(testArgs) {
372396
});
373397

374398
// For admin script and unit testing of functions
375-
module.exports = { run, validateKey, db };
399+
module.exports = { run, validateKey, saveKeys, db };

app/krs.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ exports.provisionKey = co(function *(req) {
157157

158158
yield key.save();
159159

160+
// If the master key has a signature, we include the signature in the response to the user
161+
if (masterKey.signature) {
162+
key.masterKeySig = masterKey.signature;
163+
}
164+
160165
yield masterKey.update({ $inc: { keyCount: 1 } });
161166

162167
if (!req.body.disableKRSEmail && !process.config.disableAllKRSEmail) {

app/models/masterkey.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ const masterKeySchema = new mongoose.Schema({
77
customerId: { type: String },
88
pub: { type: String },
99
path: { type: String },
10+
signature: {type: String},
1011
keyCount: { type: Number }
1112
});
1213

1314
masterKeySchema.methods = {
1415
toJSON: function() {
15-
return _.pick(this, ['type', 'coin', 'customerId', 'pub', 'path', 'keyCount']);
16+
return _.pick(this, ['type', 'coin', 'customerId', 'pub', 'path', 'signature', 'keyCount']);
1617
}
1718
};
1819

config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ module.exports = {
4040
"bitgo": "changeThisSecret"
4141
}
4242
},
43+
"verificationPub": null,
4344
"neverReuseMasterKey": true,
4445
"disableAllKRSEmail": true,
4546
"lowKeyWarningLevels": [10000, 5000, 1000, 500, 100, 0]

package-lock.json

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"dependencies": {
1818
"argparse": "^1.0.10",
1919
"bignumber.js": "^7.2.1",
20+
"bitcoinjs-message": "^2.0.0",
2021
"bitgo-utxo-lib": "^1.1.2",
2122
"body-parser": "^1.18.3",
2223
"dotenv": "^6.1.0",

test/admin.js

Lines changed: 131 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,18 @@ const should = require('should');
33
const testutils = require('./testutils');
44
const admin = require('../app/admin.js');
55
const WalletKey = require('../app/models/walletkey');
6+
const MasterKey = require('../app/models/masterkey');
67
const utils = require('../app/utils.js');
8+
const Promise = require('bluebird');
9+
const co = Promise.coroutine;
10+
let originalVerifcationPub = process.config.verificationPub;
11+
12+
const testVerificationPub = '1GS6JPCUpyFZLrE5bbJBcpeg1EdqW63nHd';
13+
const xpub = 'xpub661MyMwAqRbcGGPNi42htgwXoLuPjEtdRSRUm65GWRPAb31WPRRkxzL18TGrpU2sirNjXAHFjAEmiav9kmVfY83dTfxh3DVVwNcM9JNVebh';
14+
const xpubSig = 'IJB1Ubed4LNVvHfbH3s7i9duWmVi+98rCcGpB/t/V9xkY+VLl+YEv3w/g/79QgHYFv4D/nm2SGrT+FaOL1PomTU';
15+
const xlmPub = 'GCM67ICQVOYN7GYYHMMGVPQ7NDCLNVBM4R74IIQTG65H4DM3KGEDMW3S';
16+
const xlmSig = 'IBdvAO2063rsqo6zyhcoq4qGWMZFFQXCbcP4X7/vnbuyQO7VF2FHHr/M85CQb/yBfPgZRy5VsmDmrW3Qa6OXk6w';
17+
const badSig = 'IBadAO2063rsqo6zyhcoq4qGWMZFFQXCbcP4X7/vnbuyQO7VF2FHHr/M85CQb/yBfPgZRy5VsmDmrW3Qa6OXk6w';
718

819
describe('Offline Admin Tool', function() {
920
before(function() {
@@ -12,15 +23,18 @@ describe('Offline Admin Tool', function() {
1223

1324
after(function() {
1425
testutils.mongoose.connection.close();
26+
process.config.verificationPub = originalVerifcationPub;
1527
});
1628

1729
describe('Xpub validation', function() {
30+
1831
// Xpub importing was tested with a local file with contents: (w/o line breaks)
1932
// xpub6AHA9hZDN11k2ijHMeS5QqHx2KP9aMBRhTDqANMnwVtdyw2TDYRmF8PjpvwUFcL1Et8Hj59S3gTSMcUQ5gAqTz3Wd8EsMTmF3DChhqPQBnU,
2033
// xpub69pXXVvsBtZHWE1wgoix7xRAnbp1r6tR1kTK9cKvCd4QgYh4JBSLBmLA65Kg7rCMwGYrNHKFKxZDtjRkU4Ex2ozMYGfk14EyotJ5xjf2Goy,
2134
// xpub6AU2iYgKUY8FvUc2Nz2fcWpKU1HJTeYTbzv4MZLGPBw2YnhoZPkkUo54fvqZyVtxtszMdyksF8k3iqMcoegyvj72xKZCmuCjDWneXjjztLN
2235
// and successfully saved to local Mongo instance
2336
describe('failure', function() {
37+
process.config.verificationPub = null;
2438
it('should fail if length is not 111', function() {
2539
const SHORT_XPUB = { path: 'm/0\'', pub: 'xpub1234567890' };
2640

@@ -42,14 +56,15 @@ describe('Offline Admin Tool', function() {
4256

4357
describe('success', function() {
4458
it('should succeed with a valid key', function() {
59+
process.config.verificationPub = null;
4560
const GOOD_XPUB = { path: 'm/0\'', pub: 'xpub6B7XuUcPQ9MeszNzaTTGtni9W79MmFnHa7FUe7Hrbv3pefnaDFCHtJWaWdg1FVbocHhivnCRTCbHTjDrMBEyAGDJHGyqCnLhtEWP2rtb1sL' };
46-
4761
admin.validateKey(GOOD_XPUB, 'xpub').should.equal(true);
4862
});
4963
});
5064
});
5165

5266
describe('BIP32 child key derivation', function() {
67+
process.config.verificationPub = null;
5368
// from https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki test vector 2 chain m
5469
const MASTER_XPUB = 'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB';
5570

@@ -80,46 +95,123 @@ describe('Offline Admin Tool', function() {
8095
});
8196

8297
describe('Stellar key derivation', function() {
83-
// from test 3 at https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0005.md#test-cases
84-
const MASTER_SEED = '937ae91f6ab6f12461d9936dfc1375ea5312d097f3f1eb6fed6a82fbe38c85824da8704389831482db0433e5f6c6c9700ff1946aa75ad8cc2654d6e40f567866'
85-
86-
describe('failure', function() {
87-
it('should fail with an invalid master seed', function() {
88-
const BAD_SEED = '-thisisabadseed';
89-
90-
(function() { utils.deriveChildKey(BAD_SEED, "m/148'", 'xlm') }).should.throw(Error);
91-
});
92-
93-
it('should fail with an invalid derivation path', function() {
94-
(function() { utils.deriveChildKey(MASTER_SEED, 'derivation path', 'xlm') }).should.throw(Error);
95-
});
96-
});
97-
98-
describe('success', function() {
99-
// test 3 from https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0005.md#test-cases
100-
it("should find m/44'/148'/0' of test vector 3", function() {
101-
const pub = 'GC3MMSXBWHL6CPOAVERSJITX7BH76YU252WGLUOM5CJX3E7UCYZBTPJQ';
102-
const priv = 'SAEWIVK3VLNEJ3WEJRZXQGDAS5NVG2BYSYDFRSH4GKVTS5RXNVED5AX7';
103-
104-
const publicKey = utils.deriveChildKey(MASTER_SEED, "m/44'/148'/0'", 'xlm', true);
105-
publicKey.should.equal(pub);
106-
const secret = utils.deriveChildKey(MASTER_SEED, "m/44'/148'/0'", 'xlm', false);
107-
secret.should.equal(priv);
108-
});
109-
110-
// test 3 from https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0005.md#test-cases
111-
it("should find m/44'/148'/6' of test vector 3", function() {
112-
const pub = 'GCUDW6ZF5SCGCMS3QUTELZ6LSAH6IVVXNRPRLAUNJ2XYLCA7KH7ZCVQS';
113-
const priv = 'SBSHUZQNC45IAIRSAHMWJEJ35RY7YNW6SMOEBZHTMMG64NKV7Y52ZEO2';
114-
115-
const publicKey = utils.deriveChildKey(MASTER_SEED, "m/44'/148'/6'", 'xlm', true);
116-
publicKey.should.equal(pub);
117-
const secret = utils.deriveChildKey(MASTER_SEED, "m/44'/148'/6'", 'xlm', false);
118-
secret.should.equal(priv);
119-
});
120-
})
98+
process.config.verificationPub = null;
99+
// from test 3 at https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0005.md#test-cases
100+
const MASTER_SEED = '937ae91f6ab6f12461d9936dfc1375ea5312d097f3f1eb6fed6a82fbe38c85824da8704389831482db0433e5f6c6c9700ff1946aa75ad8cc2654d6e40f567866'
101+
102+
describe('failure', function() {
103+
it('should fail with an invalid master seed', function() {
104+
const BAD_SEED = '-thisisabadseed';
105+
(function() { utils.deriveChildKey(BAD_SEED, "m/148'", 'xlm') }).should.throw(Error);
106+
});
107+
108+
it('should fail with an invalid derivation path', function() {
109+
(function() { utils.deriveChildKey(MASTER_SEED, 'derivation path', 'xlm') }).should.throw(Error);
110+
});
111+
});
112+
describe('success', function() {
113+
// test 3 from https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0005.md#test-cases
114+
it("should find m/44'/148'/0' of test vector 3", function() {
115+
const pub = 'GC3MMSXBWHL6CPOAVERSJITX7BH76YU252WGLUOM5CJX3E7UCYZBTPJQ';
116+
const priv = 'SAEWIVK3VLNEJ3WEJRZXQGDAS5NVG2BYSYDFRSH4GKVTS5RXNVED5AX7';
117+
118+
const publicKey = utils.deriveChildKey(MASTER_SEED, "m/44'/148'/0'", 'xlm', true);
119+
publicKey.should.equal(pub);
120+
const secret = utils.deriveChildKey(MASTER_SEED, "m/44'/148'/0'", 'xlm', false);
121+
secret.should.equal(priv);
122+
});
123+
124+
// test 3 from https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0005.md#test-cases
125+
it("should find m/44'/148'/6' of test vector 3", function() {
126+
const pub = 'GCUDW6ZF5SCGCMS3QUTELZ6LSAH6IVVXNRPRLAUNJ2XYLCA7KH7ZCVQS';
127+
const priv = 'SBSHUZQNC45IAIRSAHMWJEJ35RY7YNW6SMOEBZHTMMG64NKV7Y52ZEO2';
128+
const publicKey = utils.deriveChildKey(MASTER_SEED, "m/44'/148'/6'", 'xlm', true);
129+
publicKey.should.equal(pub);
130+
const secret = utils.deriveChildKey(MASTER_SEED, "m/44'/148'/6'", 'xlm', false);
131+
secret.should.equal(priv);
132+
});
133+
})
121134
});
122135

136+
describe('Key signature verification', function() {
137+
describe('failure', function() {
138+
it('should fail xpub validation with a bad signature', function() {
139+
process.config.verificationPub = testVerificationPub;
140+
const key = {
141+
pub: xpub,
142+
signature: badSig
143+
};
144+
const valid = admin.validateKey(key,'xpub');
145+
valid.should.equal(false);
146+
});
147+
148+
it('should fail xlm validation with a bad signature', function() {
149+
process.config.verificationPub = testVerificationPub;
150+
const key = {
151+
pub: xlmPub,
152+
signature: badSig
153+
};
154+
const valid = admin.validateKey(key,'xlm');
155+
valid.should.equal(false);
156+
});
157+
158+
it('should fail xpub validation with no signature', function() {
159+
process.config.verificationPub = testVerificationPub;
160+
const key = {
161+
pub: xpub
162+
};
163+
const valid = admin.validateKey(key,'xpub');
164+
valid.should.equal(false);
165+
});
166+
167+
it('should fail xlm validation with no signature', function() {
168+
process.config.verificationPub = testVerificationPub;
169+
const key = {
170+
pub: xlmPub
171+
};
172+
const valid = admin.validateKey(key,'xlm');
173+
valid.should.equal(false);
174+
});
175+
});
176+
177+
describe('success', function() {
178+
it('should validate xpub with a good signature', function() {
179+
process.config.verificationPub = testVerificationPub;
180+
const key = {
181+
pub: xpub,
182+
signature: xpubSig
183+
};
184+
const valid = admin.validateKey(key,'xpub');
185+
valid.should.equal(true);
186+
});
187+
188+
it('should validate xlm with a good signature', function() {
189+
process.config.verificationPub = testVerificationPub;
190+
const key = {
191+
pub: xlmPub,
192+
signature: xlmSig
193+
};
194+
const valid = admin.validateKey(key,'xlm');
195+
valid.should.equal(true);
196+
});
197+
});
198+
});
199+
200+
describe('Save a key with a signature', co(function *() {
201+
it('should successfully save a key with a signature to the database', co(function *() {
202+
process.config.verificationPub = testVerificationPub;
203+
const key = {
204+
pub: xpub,
205+
signature: xpubSig,
206+
path: '0'
207+
};
208+
const keyList = [key];
209+
yield admin.saveKeys(keyList, 'xpub');
210+
const foundKey = yield MasterKey.findOne({ pub: xpub });
211+
foundKey.should.have.property('signature');
212+
}));
213+
}));
214+
123215
describe('Verification', function() {
124216
before(function() {
125217
const key = new WalletKey({

0 commit comments

Comments
 (0)