Skip to content

Commit

Permalink
General code cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
oed committed May 14, 2018
1 parent 101eed7 commit 40a4de4
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 104 deletions.
12 changes: 6 additions & 6 deletions bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ function split({ threshold, shards }) {
console.log('Please specify threshold and shards')
return
}
handlePrompt(prompts.seed, result => {
let shardMnemonics = seedsplit.split(result.seed, shards, threshold)
shardMnemonics.then((x) => console.log(x.join('\n')))
handlePrompt(prompts.seed, async result => {
let shardMnemonics = await seedsplit.split(result.seed, shards, threshold)
console.log(shardMnemonics.join('\n'))
})
}

Expand All @@ -36,9 +36,9 @@ function combine({ threshold }) {
console.log('Please specify threshold')
return
}
getMnemonics([], threshold, mnemonics => {
let seedMnemonic = seedsplit.combine(mnemonics)
seedMnemonic.then((x) => console.log(x))
getMnemonics([], threshold, async mnemonics => {
let seedMnemonic = await seedsplit.combine(mnemonics)
console.log(seedMnemonic)
})
}

Expand Down
91 changes: 30 additions & 61 deletions lib/seedsplit.js
Original file line number Diff line number Diff line change
@@ -1,82 +1,51 @@
const bip39 = require('bip39')
const ssss = require("shamirsecretsharing");

function split(seed, numShards, threshold) {
const SECRET_LENGTH = 64

const lengthToPrefix = { 64: '', 56: 'd', 48: 'c', 40: 'b', 32: 'a' }
const prefixToLength = { '': 64, 'd': 56, 'c': 48, 'b': 40, 'a': 32 }
const getExtraEntopy = length => bip39.mnemonicToEntropy(bip39.generateMnemonic(128)).substring(0, SECRET_LENGTH - length)


async function split(seed, numShards, threshold) {
if (threshold > numShards) {
throw new Error('Threshold can\'t be larger than the number of shards')
}
if (!bip39.validateMnemonic(seed)) {
throw new Error('Invalid mnemonic')
}

let ent = bip39.mnemonicToEntropy(seed)
let prefix = ""
switch(ent.length) {
case 32:
ent = ent + bip39.mnemonicToEntropy(bip39.generateMnemonic(128))
prefix = "x"
break;
case 40:
ent = ent + bip39.mnemonicToEntropy(bip39.generateMnemonic(128)).substring(0,24)
prefix = "s"
break;
case 48:
ent = ent + bip39.mnemonicToEntropy(bip39.generateMnemonic(128)).substring(0,16)
prefix = "t"
break;
case 56:
ent = ent + bip39.mnemonicToEntropy(bip39.generateMnemonic(128)).substring(0,8)
prefix = "m"
break;
}
let prefix = lengthToPrefix[ent.length]
ent = ent + getExtraEntopy(ent.length)

let shards = ssss.createKeyshares(Buffer.from(ent, 'hex'), numShards, threshold)
let shards = await ssss.createKeyshares(Buffer.from(ent, 'hex'), numShards, threshold)

return shards.then((x) => x.map(shard =>
return shards.map(shard =>
prefix + shard[0] + ' ' + bip39.entropyToMnemonic(shard.slice(1).toString('hex'))
))
)
}

function combine(shardMnemonics) {
async function combine(shardMnemonics) {
let prefix = ""
let shards = shardMnemonics.map(sm => {
if (!bip39.validateMnemonic(sm.split(' ').slice(1).join(' '))) {
throw new Error('Invalid mnemonic')
}

let buf = new Buffer.from('00' + bip39.mnemonicToEntropy(sm.split(' ').slice(1).join(' ')), 'hex')

let number = sm.split(' ')[0]
if (!/\d/.test(number[0])) {
prefix = number[0]
number = number.slice(1)
}

buf.writeUInt8(parseInt(number), 0)
return buf
})

let comb = ssss.combineKeyshares(shards)

if (!bip39.validateMnemonic(sm.split(' ').slice(1).join(' '))) {
throw new Error('Invalid mnemonic')
}
let buf = new Buffer.from('00' + bip39.mnemonicToEntropy(sm.split(' ').slice(1).join(' ')), 'hex')

let number = sm.split(' ')[0]
if (!/\d/.test(number[0])) {
prefix = number[0]
number = number.slice(1)
}
buf.writeUInt8(parseInt(number), 0)
return buf
})

let combined = await ssss.combineKeyshares(shards)
try{
return comb.then((x) => {
switch(prefix) {
case "x":
return bip39.entropyToMnemonic(x.toString('hex').substring(0, 32))
break;
case "s":
return bip39.entropyToMnemonic(x.toString('hex').substring(0, 40))
break;
case "t":
return bip39.entropyToMnemonic(x.toString('hex').substring(0, 48))
break;
case "m":
return bip39.entropyToMnemonic(x.toString('hex').substring(0, 56))
break;
default:
return bip39.entropyToMnemonic(x.toString('hex'))
}
})
return bip39.entropyToMnemonic(combined.toString('hex').substring(0, prefixToLength[prefix]))
} catch (e) {
throw new Error('Could not combine the given mnemonics')
}
Expand Down
74 changes: 37 additions & 37 deletions test/seedsplit.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,73 +40,73 @@ describe('seedsplit', () => {
return Promise.all(results)
})

it('should throw if threshold > shards', (done) => {
let fn = () => seedsplit.split(twelveWordSeeds[0], 3, 4)
assert.throws(fn)
done()
it('should throw if threshold > shards', async () => {
let didThrow = false
try {
await seedsplit.split(twelveWordSeeds[0], 3, 4)
} catch (e) {
didThrow = true
}
assert.isTrue(didThrow)
})

it('should give the correct seed with any combination of the right number of shards', (done) => {
testSufficientNumShards(shardsList1, twelveWordSeeds, 3)
testSufficientNumShards(shardsList2, twentyfourWordSeeds, 3)
testSufficientNumShards(shardsList1, twelveWordSeeds, 4)
testSufficientNumShards(shardsList2, twentyfourWordSeeds, 4)
testSufficientNumShards(shardsList1, twelveWordSeeds, 5)
testSufficientNumShards(shardsList2, twentyfourWordSeeds, 5)
it('should give the correct seed with any combination of the right number of shards', async () => {
await testSufficientNumShards(shardsList1, twelveWordSeeds, 3)
await testSufficientNumShards(shardsList2, twentyfourWordSeeds, 3)
await testSufficientNumShards(shardsList1, twelveWordSeeds, 4)
await testSufficientNumShards(shardsList2, twentyfourWordSeeds, 4)
await testSufficientNumShards(shardsList1, twelveWordSeeds, 5)
await testSufficientNumShards(shardsList2, twentyfourWordSeeds, 5)

testSufficientNumShards(shardsList3, twelveWordSeeds, 2)
testSufficientNumShards(shardsList4, twentyfourWordSeeds, 2)
testSufficientNumShards(shardsList3, twelveWordSeeds, 3)
testSufficientNumShards(shardsList4, twentyfourWordSeeds, 3)
done()
await testSufficientNumShards(shardsList3, twelveWordSeeds, 2)
await testSufficientNumShards(shardsList4, twentyfourWordSeeds, 2)
await testSufficientNumShards(shardsList3, twelveWordSeeds, 3)
await testSufficientNumShards(shardsList4, twentyfourWordSeeds, 3)
}).timeout(4000)

it('should not give correct seed with a combination of too few shards', (done) => {
testInsufficientNumShards(shardsList1, twelveWordSeeds, 1)
testInsufficientNumShards(shardsList2, twentyfourWordSeeds, 1)
testInsufficientNumShards(shardsList1, twelveWordSeeds, 2)
testInsufficientNumShards(shardsList2, twentyfourWordSeeds, 2)
it('should not give correct seed with a combination of too few shards', async () => {
await testInsufficientNumShards(shardsList1, twelveWordSeeds, 1)
await testInsufficientNumShards(shardsList2, twentyfourWordSeeds, 1)
await testInsufficientNumShards(shardsList1, twelveWordSeeds, 2)
await testInsufficientNumShards(shardsList2, twentyfourWordSeeds, 2)

testInsufficientNumShards(shardsList3, twelveWordSeeds, 1)
testInsufficientNumShards(shardsList4, twentyfourWordSeeds, 1)
done()
await testInsufficientNumShards(shardsList3, twelveWordSeeds, 1)
await testInsufficientNumShards(shardsList4, twentyfourWordSeeds, 1)
})
})

function testCorrectSplit(wordSeeds, numShards, threshold) {
async function testCorrectSplit(wordSeeds, numShards, threshold) {
let shardsList = []
for (const ws of wordSeeds) {
let shards = seedsplit.split(ws, numShards, threshold)
shardsList.push(shards.then((x) => {
assert.equal(x.length, numShards, 'should have created right number of shares')
return x
}))
let shards = await seedsplit.split(ws, numShards, threshold)
assert.equal(shards.length, numShards, 'should have created right number of shares')
shardsList.push(shards)
}
return Promise.all(shardsList)
return shardsList
}

function testSufficientNumShards(shardsList, wordSeeds, numShards) {
async function testSufficientNumShards(shardsList, wordSeeds, numShards) {
for (let i = 0; i < shardsList.length; i++) {
let cmbs = Combinatorics.combination(shardsList[i], numShards)
while (cmb = cmbs.next()) {
let combination = seedsplit.combine(cmb)
combination.then((x) => assert.equal(x, wordSeeds[i]))
let combination = await seedsplit.combine(cmb)
assert.equal(combination, wordSeeds[i])
}
}
}

function testInsufficientNumShards(shardsList, wordSeeds, numShards) {
async function testInsufficientNumShards(shardsList, wordSeeds, numShards) {
for (let i = 0; i < shardsList.length; i++) {
let cmbs = Combinatorics.combination(shardsList[i], numShards)
while (cmb = cmbs.next()) {
let combination
try {
combination = seedsplit.combine(cmb)
combination = await seedsplit.combine(cmb)
} catch (e) {
// only throws when decoded hex is not valid
assert.equal(e.message, 'Could not combine the given mnemonics')
}
combination.then((x) => assert.notEqual(x, wordSeeds[i]))
assert.notEqual(combination, wordSeeds[i])
}
}
}

0 comments on commit 40a4de4

Please sign in to comment.