{"action":"created","comment":{"url":"https://api.github.com/repos/urbit/keygen-js/pulls/comments/228290007","pull_request_review_id":168519836,"id":228290007,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIyODI5MDAwNw==","diff_hunk":"@@ -1,567 +1,363 @@\n-const crypto = require('isomorphic-webcrypto');\n-const argon2 = require('argon2-wasm');\n-const nacl = require('tweetnacl');\n-const bip32 = require('bip32');\n-const lodash = require('lodash');\n-const ob = require('urbit-ob');\n-\n-\n+const argon2 = require('argon2-wasm')\n+const bip39 = require('bip39')\n+const crypto = require('isomorphic-webcrypto')\n+const util = require('ethereumjs-util')\n+const hdkey = require('hdkey')\n+const lodash = require('lodash')\n+const nacl = require('tweetnacl')\n+const ob = require('urbit-ob')\n+\n+const CHILD_SEED_TYPES = {\n+  OWNERSHIP: 'ownership',\n+  TRANSFER: 'transfer',\n+  SPAWN: 'spawn',\n+  VOTING: 'voting',\n+  MANAGEMENT: 'management',\n+  NETWORK: 'network'\n+}\n \n /**\n  * Check if a ship is a galaxy.\n- * @param  {integer}  ship\n- * @return  {bool}  true if galaxy, false otherwise\n- */\n-const isGalaxy = ship => Number.isInteger(ship) && ship >= 0 && ship < 256;\n-\n-\n-\n-/**\n- * Split a string at the provided index, returning both chunks.\n- * @param  {integer}  index the index to split at\n- * @param  {string}  string a string\n- * @return  {array of strings}  the split string\n- */\n-const splitAt = (index, str) => [str.slice(0, index), str.slice(index)];\n-\n-\n-\n-/**\n- * if any is undefined, return d. Otherwise return any\n- * @param  {any} a value to check if defined\n- * @param  {any} d   value to swap in if undefined.\n- * @return {any}  either a or d\n- */\n-const defaultTo = (a, d) => isUndefined(a) ? d : a;\n-\n-\n-\n-/**\n- * get a value from an object with a key. If no value is found or object is\n- * undefined, return d\n- * @param  {object} o The object to pull from.\n- * @param  {string} k The key to use.\n- * @param  {any} d The default value to swap in eith o or k is undefined.\n- * @return {any}\n- */\n-const get = (o, k, d) => {\n-  if (isUndefined(o)) return d;\n-  const r = o[k];\n-  return isUndefined(r) ? d : r;\n-};\n-\n-\n-\n-/**\n- * returns true if a is undefined, false if not.\n- * @param  {any}  a the value to check\n- * @return {Boolean}\n- */\n-const isUndefined = a => typeof a === 'undefined';\n-\n-\n-\n-/**\n- * returns true if a is a number, false if not.\n- * @param  {any}  any the value to check\n- * @return {Boolean}\n+ * @param  {Number}  ship\n+ * @return  {Bool}  true if galaxy, false otherwise\n  */\n-const isNumber = a => typeof a === 'number' && isFinite(a);\n-\n-\n+const isGalaxy = ship =>\n+  lodash.isInteger(ship) && ship >= 0 && ship < 256\n \n /**\n- * Converts a buffer to hexidecimal string\n- * @param  {Buffer} buffer\n- * @return {string}\n+ * Encode a buffer as hex.\n+ * @param  {Buffer}  buffer\n+ * @return  {String}  hex-encoded buffer\n  */\n const buf2hex = buffer => {\n   return Array.from(new Uint8Array(buffer))\n     .map(b => b.toString(16).padStart(2, '0'))\n-    .join('');\n-};\n-\n-\n-\n-/**\n- * Converts a hexidecimal string to a buffer.\n- * @param  {string} a hex-encoded string\n- * @return {Buffer}\n- */\n-const hex2buf = hex => {\n-  return Buffer.from(hex, 'hex');\n-};\n-\n-\n-\n-/**\n- * executes SHA-512 on any size input\n- * @param  {Array, ArrayBuffer, Buffer, string} args any number of arguments\n- * @return {Promise => ArrayBuffer} Promise that resolves to arrayBuffer\n- */\n-const hash = async (...args) => {\n-  // map args into buffers and concat into one buffer\n-  const buffer = Buffer.concat(args.map(a => Buffer.from(a)));\n-  // generate a SHA-512 hash from input buffer\n-  return crypto.subtle.digest({ name: 'SHA-512' }, buffer);\n-};\n-\n+    .join('')\n+}\n \n \n /**\n- * Runs argon2wasm to return a seed of desired bytes\n- * @param  {Uint8Array, Buffer, string} entropy ticket bytes as string or\n- * Uint8Array or Buffer, at least 16 bytes\n- * @param  {int} seedSize desired size of the generated seeds in bytes\n- * @return {Promise => ArrayBuffer} Promise that resolves to arrayBuffer\n+ * Derive a 256-bit key from provided entropy via Argon2.\n+ *\n+ * @param  {String}  entropy\n+ * @return {Promise<Object>} derived key\n  */\n-const argon2u = (entropy, seedSize) => argon2.hash({\n-  pass: entropy, // string or Uint8Array\n+const argon2u = entropy => argon2.hash({\n+  pass: entropy,\n   salt: 'urbitkeygen',\n   type: argon2.types.Argon2u,\n-  hashLen: seedSize,\n-  // distPath: 'node_modules/argon2-wasm/dist',\n+  hashLen: 32,\n   parallelism: 4,\n   mem: 512000,\n   time: 1,\n-});\n-\n+})\n \n+/**\n+ * SHA-256 hash function.\n+ *\n+ * @param  {Array, ArrayBuffer, Buffer, String} args any number of arguments\n+ * @return {Promise<ArrayBuffer>}  the hash, as bytes\n+ */\n+const sha256 = async (...args) => {\n+  const buffer = Buffer.concat(args.map(Buffer.from))\n+  return crypto.subtle.digest({ name: 'SHA-256' }, buffer)\n+}\n \n /**\n- * Derive a new seed from a seed. Uses a config with the following entries:\n- * @param  {Buffer}   seed seed to derive from.\n- * @param  {string}   type the type of the seed we want to derive:\n- * (\"transfer\", \"spawn\", \"voting\", \"manage\", \"network\").\n- * @param  {object}   revision the revision number of the seed we want to derive.\n- * @param  {integer}  ship  optional ship number we want to derive the seed for.\n- * @param  {string}   password  optional password to salt the seed with before\n- * deriving.\n- * @return {Promise => Buffer} a new seed\n+ * Derive a child seed from a parent.\n+ *\n+ * @param  {Array, ArrayBuffer, Buffer, String}  seed a parent seed\n+ * @param  {String}  type the type of child seed to derive\n+ * @param  {Number}  ship the ship to derive the seed for\n+ * @param  {Number}  revision the revision number\n+ * @return {Promise<String>} the BIP39 child mnemonic\n  */\n const childSeedFromSeed = async config => {\n-  const { seed, type, revision, ship, password } = config;\n-\n-  const salt = isNumber(ship)\n-    ? `${type}-${revision}-${ship}`\n-    : `${type}-${revision}`;\n-\n-  const childSeed = await hash(seed, salt, defaultTo(password, ''));\n-\n-  return childSeed.slice(0, seed.byteLength || seed.length);\n-};\n+  const { seed, type, ship, revision } = config\n+  const salt = lodash.isNull(ship) ? '' : `${ship}`\n+  const hash = await sha256(seed, type, salt, `${revision}`)\n+  return type !== CHILD_SEED_TYPES.NETWORK\n+    ? bip39.entropyToMnemonic(hash)\n+    : Buffer.from(hash).toString('hex')\n+}\n \n \n \n /**\n- * Derive a new node from a seed. Uses a config with the following entries:\n- * @param  {Buffer}   seed seed to derive from.\n- * @param  {string}   type the type of the seed we want to derive:\n- * (\"transfer\", \"spawn\", \"voting\", \"manage\", \"network\").\n- * @param  {integer}  revision the revision number of the seed we want to derive.\n- * @param  {integer}  ship  optional ship number we want to derive the seed for.\n- * @param  {string}   password  optional password to salt the seed with before\n- * deriving.\n- * @return {Promise => Object} a new node\n+ * Derive a child BIP32 node from a parent seed.\n+ *\n+ * @param  {Array, ArrayBuffer, Buffer, String}  seed a parent seed\n+ * @param  {String}  type the type of child node to derive\n+ * @param  {Number}  ship the ship to derive the node for\n+ * @param  {Number}  revision the revision number\n+ * @return {Promise<Object>} the BIP32 child node\n  */\n const childNodeFromSeed = async config => {\n-  const { seed, type, revision, ship, password } = config;\n-  const childSeed = await childSeedFromSeed({seed, type, revision, ship, password});\n+  const { type, ship, revision, password } = config\n+  const child = await childSeedFromSeed(config)\n   return {\n     meta: {\n-      type,\n-      revision: defaultTo(revision, 0),\n-      ship: !isUndefined(ship)\n-        ? ship\n-        : null\n+      type: type,\n+      revision: revision,\n+      ship: ship\n     },\n-    seed: buf2hex(childSeed),\n-    keys: await walletFromSeed(childSeed, password),\n-  };\n-};\n+    seed: child,\n+    keys: bip32NodeFromSeed(child, password)\n+  }\n+}\n \n \n \n /**\n- * Derive a BIP32 master node from a seed.\n- * @param  {string, Buffer}  seed     seed to derive from.\n- * @param  {string}          password optional password to salt the seed with before\n- * deriving.\n- * @return {Promise => Object} a wallet derived according to BIP32 from the\n- *  SHA-512 hash of the seed+password.\n+ * Derive a BIP32 master node -- supplemented with a corresponding Ethereum\n+ * address -- from a seed.\n+ *\n+ * @param  {String}  seed a BIP39 mnemonic\n+ * @param  {String}  password an optional password to use when generating the\n+ *   BIP39 seed\n+ * @return {Object} a BIP32 node\n  */\n-const walletFromSeed = async (seed, password) => {\n-  // we hash the seed with SHA-512 before doing BIP32 wallet generation,\n-  // because BIP32 doesn't support seeds of bit-lengths < 128 or > 512.\n-  const seedHash = await hash(seed, defaultTo(password, ''));\n-  const { publicKey, privateKey, chainCode } = bip32.fromSeed(Buffer.from(seedHash));\n-  return {\n-    public: buf2hex(publicKey),\n-    private: buf2hex(privateKey),\n-    chain: buf2hex(chainCode),\n-  };\n-};\n+const bip32NodeFromSeed = (mnemonic, password) => {\n+  const seed = bip39.mnemonicToSeed(mnemonic, password)\n+  const hd = hdkey.fromMasterSeed(seed)\n+  const path = \"m/44'/60'/0'/0/0\"\n+  const wallet = hd.derive(path)\n \n+  const publicKey = buf2hex(wallet.publicKey)\n+  const privateKey = buf2hex(wallet.privateKey)\n+  const chain = buf2hex(wallet.chainCode)\n+  const address = addressFromSecp256k1Public(publicKey)\n \n-\n-/**\n- * Wraps nacl.lowlvel.crypto_hash\n- * @param  {Uint8Array} seed\n- * @return {Array}\n- */\n-const naclHash = seed => {\n-  let newHash = []\n-  nacl.lowlevel.crypto_hash(newHash, seed.reverse(), seed.length)\n-  return newHash\n+  return {\n+    public: publicKey,\n+    private: privateKey,\n+    chain,\n+    address\n+  }\n }\n \n \n \n /**\n- * Derive Urbit network keypairs from a seed. Matches ++pit:nu:crub:crypto\n+ * Derive Urbit network keypairs from a seed.  Matches ++pit:nu:crub:crypto\n  * @param  {Buffer} seed     seed to derive from\n- * @param  {Buffer} password optional password to salt the seed before deriving\n- * @return {object} urbitKeys, derived according to ++pit:nu:crub:crypto.\n+ * @return {Object} resulting Urbit network keys\n  */\n-const urbitKeysFromSeed = (seed, password) => {\n-  const h = naclHash(Buffer.concat([seed, password]));\n+const urbitKeysFromSeed = seed => {\n+  let h = []\n+  nacl.lowlevel.crypto_hash(h, seed.reverse(), seed.length)\n \n-  const c = h.slice(32);\n-  const a = h.slice(0, 32);\n+  const c = h.slice(32)\n+  const a = h.slice(0, 32)\n \n-  const crypt = nacl.sign.keyPair.fromSeed(Buffer.from(c));\n-  const auth = nacl.sign.keyPair.fromSeed(Buffer.from(a));\n+  const crypt = nacl.sign.keyPair.fromSeed(Buffer.from(c))\n+  const auth = nacl.sign.keyPair.fromSeed(Buffer.from(a))\n+\n+  const crypt_pub = buf2hex(crypt.publicKey.reverse())\n+  const auth_pub = buf2hex(auth.publicKey.reverse())\n \n   return {\n     crypt: {\n       private: buf2hex(c.reverse()),\n-      public: buf2hex(crypt.publicKey.reverse()),\n+      public: crypt_pub,\n+      address: addressFromNetworkPublic(crypt_pub)\n     },\n     auth: {\n       private: buf2hex(a.reverse()),\n-      public: buf2hex(auth.publicKey.reverse()),\n+      public: auth_pub,\n+      address: addressFromNetworkPublic(auth_pub)\n     }\n-  };\n-};\n-\n-\n-\n-/**\n- * Perform a sanity check on a set of three shards, checking that any two of\n- * them can be combined to produce a reference value.\n- * @param  {array of strings => string}  combiner a shard-combining function\n- * @param  {string}  ref a reference value for comparison\n- * @param  {string => string => bool}  comp a comparison function\n- * @param  {array of strings}  shards an array of shards\n- * @return  {bool}  true if shards consistent\n- */\n-const shardsConsistent = (combiner, ref, comp, shards) => {\n-  const set0 = shards.slice(0, 2);\n-  const set1 = shards.slice(1);\n-  const set2 = [shards[0], shards[2]];\n-\n-  const check0 = comp(combiner(set0), ref);\n-  const check1 = comp(combiner(set1), ref);\n-  const check2 = comp(combiner(set2), ref);\n-\n-  return check0 && check1 && check2;\n-}\n-\n-\n-\n-/**\n- * Reduce a collection of arrays by recursive applications of bytewise XOR.\n- * @param  {Array of Array of integers}  arrays an array of arrays\n- * @return {Array} the resulting array\n- */\n-const reduceByXor = (arrays) => {\n-  return arrays.reduce((acc, arr) =>\n-    lodash.zipWith(acc, arr, (x, y) => x ^ y));\n-}\n-\n-\n-\n-/**\n- * Encode a hex string as three shards, such that any two shards can be\n- * combined to recover it.\n- *\n- * @param  {string}  string hex-encoded string\n- * @return {Array of strings} resulting shards\n- */\n-const shardHex = hex => {\n-  const buffer = hex2buf(hex);\n-  const sharded = shardBuffer(buffer);\n-  const shards = sharded.map(pair =>\n-    lodash.reduce(pair, (acc, arr) =>\n-      acc + buf2hex(Buffer.from(arr)), ''));\n-\n-  if (shardsConsistent(combineHex, hex, lodash.isEqual, shards)) {\n-    return shards;\n-  } else {\n-    // shards must always be consistent, so this should be unreachable\n-    /* istanbul ignore next */\n-    throw(\"shardHex: inconsistent shards -- please report this as a bug\");\n   }\n }\n \n \n \n /**\n- * Encode a @q-encoded string as three shards, such that any two shards can be\n- * combined to recover it.\n- *\n- * @param  {string}  string @q-encoded string\n- * @return {Array of strings} resulting shards\n+ * Convert a hex-encoded secp256k1 public key into an Ethereum address.\n+ * @param  {String}  pub a (compressed) hex-encoded public key\n+ * @return  {String}  the corresponding Ethereum address\n  */\n-const shardPatq = patq => {\n-  const hexed = shardHex(ob.patq2hex(patq))\n-  const shards = hexed.map(ob.hex2patq)\n-\n-  if (shardsConsistent(combinePatq, patq, ob.eqPatq, shards)) {\n-    return shards;\n-  } else {\n-    // shards must always be consistent, so this should be unreachable\n-    /* istanbul ignore next */\n-    throw(\"shardPatq: inconsistent shards -- please report this as a bug\");\n-  }\n+const addressFromSecp256k1Public = pub => {\n+  const hashed = util.keccak256(Buffer.from(pub, 'hex'))\n+  const addr = util.addHexPrefix(hashed.slice(12).toString('hex'))\n+  return util.toChecksumAddress(addr)\n }\n \n \n \n /**\n- * Produce three shards from a buffer such that any two of them can be used to\n- * reconstruct it.\n- * @param  {Buffer}  buffer arbitrary buffer\n- * @return {Array of Array of integers} sharded buffer\n+ * Convert a hex-encoded secp256k1 private key into an Ethereum address.\n+ * @param  {String}  pub a hex-encoded private key\n+ * @return  {String}  the corresponding Ethereum address\n  */\n-const shardBuffer = buffer => {\n-  const r1 = crypto.getRandomValues(new Uint8Array(buffer.length));\n-  const r2 = crypto.getRandomValues(new Uint8Array(buffer.length));\n-\n-  const k  = Array.from(buffer);\n-  const k1 = Array.from(r1);\n-  const k2 = Array.from(r2);\n-\n-  const k0 = reduceByXor([k, k1, k2]);\n-\n-  const anyEqual =\n-       lodash.isEqual(k0, k1)\n-    || lodash.isEqual(k0, k2)\n-    || lodash.isEqual(k1, k2)\n-\n-  return anyEqual\n-    ? shardBuffer(buffer)\n-    : [[k0, k1], [k0, k2], [k1, k2]];\n+const addressFromSecp256k1Private = priv => {\n+  const pub = util.secp256k1.publicKeyCreate(Buffer.from(priv, 'hex'))\n+  return addressFromSecp256k1Public(pub)\n }\n \n \n \n /**\n- * Combine pieces of a sharded buffer together to recover the original buffer.\n- * @param  {Array of Array of integers}  shards a collection of shards\n- * @return {Buffer} the unsharded buffer\n+ * Convert a hex-encoded Ed25519-variant Urbit public network key into an\n+ * Ethereum address.\n+ * @param  {String}  pub a hex-encoded public key\n+ * @return  {String}  the corresponding Ethereum address\n  */\n-const combineBuffer = shards => {\n-  const flattened = lodash.flatten(shards);\n-  const uniques = lodash.uniqWith(flattened, lodash.isEqual);\n-  const reduced = reduceByXor(uniques);\n-  return Buffer.from(reduced);\n+const addressFromNetworkPublic = pub => {\n+  const hashed = util.keccak256(Buffer.from(pub, 'hex'))\n+  const addr = util.addHexPrefix(hashed.slice(12).toString('hex'))\n+  return util.toChecksumAddress(addr)\n }\n \n \n \n /**\n- * Combine hex-encoded shards together to reconstruct a secret.\n- * @param  {Array of Array of strings}  shards a collection of hex-encoded\n- *  shards\n- * @return {string} the reconstructed secret\n+ * Break a 384-bit ticket into three shards, any two of which can be used to\n+ * recover it.\n+ *\n+ * Each shard is simply 2/3 of the ticket -- the first third, second third, and\n+ * first and last thirds concatenated together.\n+ *\n+ * @param  {String} ticket a 384-bit @q ticket\n+ * @return {Array<String>}\n  */\n-const combineHex = shards => {\n-  const splat = shards.map(shard =>\n-    splitAt(shard.length / 2, shard));\n-  const buffers = splat.map(pair =>\n-    pair.map(buf => Array.from(hex2buf(buf))));\n-  const combined = combineBuffer(buffers);\n-  return buf2hex(combined);\n-}\n+const shard = ticket => {\n+  const ticketHex = ob.patq2hex(ticket)\n+  const ticketBuf = Buffer.from(ticketHex, 'hex')\n \n+  if (ticketBuf.length !== 48) {\n+    return [ ticket ]\n+  }\n \n+  const shard0 = ticketBuf.slice(0, 32)\n+  const shard1 = ticketBuf.slice(16)\n+  const shard2 = Buffer.concat([\n+    ticketBuf.slice(0, 16),\n+    ticketBuf.slice(32)\n+  ])\n \n-/**\n- * Combine @q-encoded shards together to reconstruct a secret.\n- * @param  {Array of Array of strings}  shards a collection of @q-encoded\n- *  shards\n- * @return {string} the reconstructed secret\n- */\n-const combinePatq = shards => {\n-  const hexed = shards.map(ob.patq2hex)\n-  const max = lodash.reduce(hexed, (acc, hex) =>\n-    hex.length > acc\n-      ? hex.length\n-      : acc\n-    , 0)\n-\n-  const padded = hexed.map(hex => hex.padStart(max, '0'))\n-\n-  const combined = combineHex(padded)\n-  return ob.hex2patq(combined)\n+  return lodash.map([shard0, shard1, shard2], buf =>\n+    ob.hex2patq(buf.toString('hex')))\n }\n \n \n \n /**\n- * Convert a full wallet into a sharded wallet.  Transforms the owner's seed\n- * into a number of shards, of which only a subset are required in order to\n- * reconstruct the original.\n+ * Generate an Urbit HD wallet given the provided configuration.\n  *\n- * @param  {object}  wallet full HD wallet\n- * @return  {object} an object representing a sharded full HD wallet\n+ * @param  {String}  ticket a 64, 128, or 384-bit @q master ticket\n+ * @param  {Number}  ship an optional ship number\n+ * @param  {String}  password a password used to salt generated seeds (default:\n+ *   null)\n+ * @param  {Number}  revision a revision number used as a salt (default: 0)\n+ * @param  {Bool}  boot if true, generate network keys for the provided ship\n+ *   (default: false)\n+ * @return  {Promise<Object>}\n  */\n-const shardWallet = wallet => {\n-  const walletCopy = lodash.cloneDeep(wallet);\n-  const sharded = shardPatq(walletCopy.ticket)\n-  walletCopy.ticket = sharded;\n-  return walletCopy;\n-}\n+const generateWallet = async config => {\n+  const { ticket } = config\n+  const ship = 'ship' in config ? config.ship : null","path":"src/index.js","position":592,"original_position":592,"commit_id":"9871e7bdcef1610a0cd739ec91718591657ce91d","original_commit_id":"9871e7bdcef1610a0cd739ec91718591657ce91d","user":{"login":"Fang-","id":3829764,"node_id":"MDQ6VXNlcjM4Mjk3NjQ=","avatar_url":"https://avatars0.githubusercontent.com/u/3829764?v=4","gravatar_id":"","url":"https://api.github.com/users/Fang-","html_url":"https://github.com/Fang-","followers_url":"https://api.github.com/users/Fang-/followers","following_url":"https://api.github.com/users/Fang-/following{/other_user}","gists_url":"https://api.github.com/users/Fang-/gists{/gist_id}","starred_url":"https://api.github.com/users/Fang-/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Fang-/subscriptions","organizations_url":"https://api.github.com/users/Fang-/orgs","repos_url":"https://api.github.com/users/Fang-/repos","events_url":"https://api.github.com/users/Fang-/events{/privacy}","received_events_url":"https://api.github.com/users/Fang-/received_events","type":"User","site_admin":false},"body":"As with the salt, I think we default to `0` rather than complete absence. @msutherl ?","created_at":"2018-10-25T18:40:21Z","updated_at":"2018-10-25T18:44:02Z","html_url":"https://github.com/urbit/keygen-js/pull/34#discussion_r228290007","pull_request_url":"https://api.github.com/repos/urbit/keygen-js/pulls/34","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/urbit/keygen-js/pulls/comments/228290007"},"html":{"href":"https://github.com/urbit/keygen-js/pull/34#discussion_r228290007"},"pull_request":{"href":"https://api.github.com/repos/urbit/keygen-js/pulls/34"}}},"pull_request":{"url":"https://api.github.com/repos/urbit/keygen-js/pulls/34","id":225713387,"node_id":"MDExOlB1bGxSZXF1ZXN0MjI1NzEzMzg3","html_url":"https://github.com/urbit/keygen-js/pull/34","diff_url":"https://github.com/urbit/keygen-js/pull/34.diff","patch_url":"https://github.com/urbit/keygen-js/pull/34.patch","issue_url":"https://api.github.com/repos/urbit/keygen-js/issues/34","number":34,"state":"open","locked":false,"title":"Match updated wallet spec","user":{"login":"jtobin","id":1414434,"node_id":"MDQ6VXNlcjE0MTQ0MzQ=","avatar_url":"https://avatars1.githubusercontent.com/u/1414434?v=4","gravatar_id":"","url":"https://api.github.com/users/jtobin","html_url":"https://github.com/jtobin","followers_url":"https://api.github.com/users/jtobin/followers","following_url":"https://api.github.com/users/jtobin/following{/other_user}","gists_url":"https://api.github.com/users/jtobin/gists{/gist_id}","starred_url":"https://api.github.com/users/jtobin/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jtobin/subscriptions","organizations_url":"https://api.github.com/users/jtobin/orgs","repos_url":"https://api.github.com/users/jtobin/repos","events_url":"https://api.github.com/users/jtobin/events{/privacy}","received_events_url":"https://api.github.com/users/jtobin/received_events","type":"User","site_admin":false},"body":"This is a bigger PR than I'd normally want to make, but there were a lot of changes to make, and I want to get them all in before this week is out.  It thus wound up being easier to just go into bull-in-china-shop mode than be particularly surgical here.  So, this covers a bit of ground.  From the CHANGELOG:\r\n\r\n* Fix all seeds at 256 bits.\r\n* Always derive BIP32 nodes according to the path m/44'/60'/0'/0/0.\r\n* Remove the optional 'password' salt.\r\n* Render and handle (non-network) seeds as BIP39 mnemonics.\r\n* Displays corresponding Ethereum addresses in derived BIP32 nodes.\r\n* Replaces XOR-based 2-3 sharding with concat-based 2-3 sharding.  Shards\r\n   are also now reported in the 'shards' property of the final wallet.\r\n* Add an 'ownership' seed, plus slight changes to other seed type names.\r\n* Miscellaneous housekeeping and simplification throughout.\r\n\r\nI believe this resolves #32, #27, #26, #25, and #22, and takes us to the [updated wallet spec](https://docs.google.com/document/d/1F_fx6P0riv5LjYI3DyLfL7L7UDkjm1n5d3nJWrXln94/edit) and also addresses various concerns from the auditors' reports.\r\n\r\nThe housekeeping/simplification axed a few hundred lines of code in `src/index.js`, and the test suite slimmed down as a result.  I also made some improvements there -- added some property tests, improved whole-wallet unit tests, etc.\r\n\r\nFor reviewing: I would ignore the diff and just read/skim the new `src/index.js` from top to bottom, perhaps with reference to the updated spec (which it maps to very clearly).  It's not very long, and should be pretty clear throughout.","created_at":"2018-10-25T10:57:44Z","updated_at":"2018-10-25T18:44:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":"aaa54455f42eebe25ffade2e03f937d4f033e8eb","assignee":null,"assignees":[],"requested_reviewers":[{"login":"gavinpatkinson","id":5265021,"node_id":"MDQ6VXNlcjUyNjUwMjE=","avatar_url":"https://avatars1.githubusercontent.com/u/5265021?v=4","gravatar_id":"","url":"https://api.github.com/users/gavinpatkinson","html_url":"https://github.com/gavinpatkinson","followers_url":"https://api.github.com/users/gavinpatkinson/followers","following_url":"https://api.github.com/users/gavinpatkinson/following{/other_user}","gists_url":"https://api.github.com/users/gavinpatkinson/gists{/gist_id}","starred_url":"https://api.github.com/users/gavinpatkinson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/gavinpatkinson/subscriptions","organizations_url":"https://api.github.com/users/gavinpatkinson/orgs","repos_url":"https://api.github.com/users/gavinpatkinson/repos","events_url":"https://api.github.com/users/gavinpatkinson/events{/privacy}","received_events_url":"https://api.github.com/users/gavinpatkinson/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/urbit/keygen-js/pulls/34/commits","review_comments_url":"https://api.github.com/repos/urbit/keygen-js/pulls/34/comments","review_comment_url":"https://api.github.com/repos/urbit/keygen-js/pulls/comments{/number}","comments_url":"https://api.github.com/repos/urbit/keygen-js/issues/34/comments","statuses_url":"https://api.github.com/repos/urbit/keygen-js/statuses/9871e7bdcef1610a0cd739ec91718591657ce91d","head":{"label":"urbit:jt-updated-wallet-spec","ref":"jt-updated-wallet-spec","sha":"9871e7bdcef1610a0cd739ec91718591657ce91d","user":{"login":"urbit","id":5237680,"node_id":"MDEyOk9yZ2FuaXphdGlvbjUyMzc2ODA=","avatar_url":"https://avatars3.githubusercontent.com/u/5237680?v=4","gravatar_id":"","url":"https://api.github.com/users/urbit","html_url":"https://github.com/urbit","followers_url":"https://api.github.com/users/urbit/followers","following_url":"https://api.github.com/users/urbit/following{/other_user}","gists_url":"https://api.github.com/users/urbit/gists{/gist_id}","starred_url":"https://api.github.com/users/urbit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/urbit/subscriptions","organizations_url":"https://api.github.com/users/urbit/orgs","repos_url":"https://api.github.com/users/urbit/repos","events_url":"https://api.github.com/users/urbit/events{/privacy}","received_events_url":"https://api.github.com/users/urbit/received_events","type":"Organization","site_admin":false},"repo":{"id":143151122,"node_id":"MDEwOlJlcG9zaXRvcnkxNDMxNTExMjI=","name":"keygen-js","full_name":"urbit/keygen-js","private":false,"owner":{"login":"urbit","id":5237680,"node_id":"MDEyOk9yZ2FuaXphdGlvbjUyMzc2ODA=","avatar_url":"https://avatars3.githubusercontent.com/u/5237680?v=4","gravatar_id":"","url":"https://api.github.com/users/urbit","html_url":"https://github.com/urbit","followers_url":"https://api.github.com/users/urbit/followers","following_url":"https://api.github.com/users/urbit/following{/other_user}","gists_url":"https://api.github.com/users/urbit/gists{/gist_id}","starred_url":"https://api.github.com/users/urbit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/urbit/subscriptions","organizations_url":"https://api.github.com/users/urbit/orgs","repos_url":"https://api.github.com/users/urbit/repos","events_url":"https://api.github.com/users/urbit/events{/privacy}","received_events_url":"https://api.github.com/users/urbit/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/urbit/keygen-js","description":"Key derivation and HD wallet generation functions for Urbit","fork":false,"url":"https://api.github.com/repos/urbit/keygen-js","forks_url":"https://api.github.com/repos/urbit/keygen-js/forks","keys_url":"https://api.github.com/repos/urbit/keygen-js/keys{/key_id}","collaborators_url":"https://api.github.com/repos/urbit/keygen-js/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/urbit/keygen-js/teams","hooks_url":"https://api.github.com/repos/urbit/keygen-js/hooks","issue_events_url":"https://api.github.com/repos/urbit/keygen-js/issues/events{/number}","events_url":"https://api.github.com/repos/urbit/keygen-js/events","assignees_url":"https://api.github.com/repos/urbit/keygen-js/assignees{/user}","branches_url":"https://api.github.com/repos/urbit/keygen-js/branches{/branch}","tags_url":"https://api.github.com/repos/urbit/keygen-js/tags","blobs_url":"https://api.github.com/repos/urbit/keygen-js/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/urbit/keygen-js/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/urbit/keygen-js/git/refs{/sha}","trees_url":"https://api.github.com/repos/urbit/keygen-js/git/trees{/sha}","statuses_url":"https://api.github.com/repos/urbit/keygen-js/statuses/{sha}","languages_url":"https://api.github.com/repos/urbit/keygen-js/languages","stargazers_url":"https://api.github.com/repos/urbit/keygen-js/stargazers","contributors_url":"https://api.github.com/repos/urbit/keygen-js/contributors","subscribers_url":"https://api.github.com/repos/urbit/keygen-js/subscribers","subscription_url":"https://api.github.com/repos/urbit/keygen-js/subscription","commits_url":"https://api.github.com/repos/urbit/keygen-js/commits{/sha}","git_commits_url":"https://api.github.com/repos/urbit/keygen-js/git/commits{/sha}","comments_url":"https://api.github.com/repos/urbit/keygen-js/comments{/number}","issue_comment_url":"https://api.github.com/repos/urbit/keygen-js/issues/comments{/number}","contents_url":"https://api.github.com/repos/urbit/keygen-js/contents/{+path}","compare_url":"https://api.github.com/repos/urbit/keygen-js/compare/{base}...{head}","merges_url":"https://api.github.com/repos/urbit/keygen-js/merges","archive_url":"https://api.github.com/repos/urbit/keygen-js/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/urbit/keygen-js/downloads","issues_url":"https://api.github.com/repos/urbit/keygen-js/issues{/number}","pulls_url":"https://api.github.com/repos/urbit/keygen-js/pulls{/number}","milestones_url":"https://api.github.com/repos/urbit/keygen-js/milestones{/number}","notifications_url":"https://api.github.com/repos/urbit/keygen-js/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/urbit/keygen-js/labels{/name}","releases_url":"https://api.github.com/repos/urbit/keygen-js/releases{/id}","deployments_url":"https://api.github.com/repos/urbit/keygen-js/deployments","created_at":"2018-08-01T12:09:56Z","updated_at":"2018-10-24T06:18:03Z","pushed_at":"2018-10-25T17:21:53Z","git_url":"git://github.com/urbit/keygen-js.git","ssh_url":"git@github.com:urbit/keygen-js.git","clone_url":"https://github.com/urbit/keygen-js.git","svn_url":"https://github.com/urbit/keygen-js","homepage":"","size":1690,"stargazers_count":2,"watchers_count":2,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"archived":false,"open_issues_count":6,"license":null,"forks":2,"open_issues":6,"watchers":2,"default_branch":"master"}},"base":{"label":"urbit:master","ref":"master","sha":"2682830ba22f00b81a1d9f94c5ac495e7596bbb7","user":{"login":"urbit","id":5237680,"node_id":"MDEyOk9yZ2FuaXphdGlvbjUyMzc2ODA=","avatar_url":"https://avatars3.githubusercontent.com/u/5237680?v=4","gravatar_id":"","url":"https://api.github.com/users/urbit","html_url":"https://github.com/urbit","followers_url":"https://api.github.com/users/urbit/followers","following_url":"https://api.github.com/users/urbit/following{/other_user}","gists_url":"https://api.github.com/users/urbit/gists{/gist_id}","starred_url":"https://api.github.com/users/urbit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/urbit/subscriptions","organizations_url":"https://api.github.com/users/urbit/orgs","repos_url":"https://api.github.com/users/urbit/repos","events_url":"https://api.github.com/users/urbit/events{/privacy}","received_events_url":"https://api.github.com/users/urbit/received_events","type":"Organization","site_admin":false},"repo":{"id":143151122,"node_id":"MDEwOlJlcG9zaXRvcnkxNDMxNTExMjI=","name":"keygen-js","full_name":"urbit/keygen-js","private":false,"owner":{"login":"urbit","id":5237680,"node_id":"MDEyOk9yZ2FuaXphdGlvbjUyMzc2ODA=","avatar_url":"https://avatars3.githubusercontent.com/u/5237680?v=4","gravatar_id":"","url":"https://api.github.com/users/urbit","html_url":"https://github.com/urbit","followers_url":"https://api.github.com/users/urbit/followers","following_url":"https://api.github.com/users/urbit/following{/other_user}","gists_url":"https://api.github.com/users/urbit/gists{/gist_id}","starred_url":"https://api.github.com/users/urbit/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/urbit/subscriptions","organizations_url":"https://api.github.com/users/urbit/orgs","repos_url":"https://api.github.com/users/urbit/repos","events_url":"https://api.github.com/users/urbit/events{/privacy}","received_events_url":"https://api.github.com/users/urbit/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/urbit/keygen-js","description":"Key derivation and HD wallet generation functions for Urbit","fork":false,"url":"https://api.github.com/repos/urbit/keygen-js","forks_url":"https://api.github.com/repos/urbit/keygen-js/forks","keys_url":"https://api.github.com/repos/urbit/keygen-js/keys{/key_id}","collaborators_url":"https://api.github.com/repos/urbit/keygen-js/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/urbit/keygen-js/teams","hooks_url":"https://api.github.com/repos/urbit/keygen-js/hooks","issue_events_url":"https://api.github.com/repos/urbit/keygen-js/issues/events{/number}","events_url":"https://api.github.com/repos/urbit/keygen-js/events","assignees_url":"https://api.github.com/repos/urbit/keygen-js/assignees{/user}","branches_url":"https://api.github.com/repos/urbit/keygen-js/branches{/branch}","tags_url":"https://api.github.com/repos/urbit/keygen-js/tags","blobs_url":"https://api.github.com/repos/urbit/keygen-js/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/urbit/keygen-js/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/urbit/keygen-js/git/refs{/sha}","trees_url":"https://api.github.com/repos/urbit/keygen-js/git/trees{/sha}","statuses_url":"https://api.github.com/repos/urbit/keygen-js/statuses/{sha}","languages_url":"https://api.github.com/repos/urbit/keygen-js/languages","stargazers_url":"https://api.github.com/repos/urbit/keygen-js/stargazers","contributors_url":"https://api.github.com/repos/urbit/keygen-js/contributors","subscribers_url":"https://api.github.com/repos/urbit/keygen-js/subscribers","subscription_url":"https://api.github.com/repos/urbit/keygen-js/subscription","commits_url":"https://api.github.com/repos/urbit/keygen-js/commits{/sha}","git_commits_url":"https://api.github.com/repos/urbit/keygen-js/git/commits{/sha}","comments_url":"https://api.github.com/repos/urbit/keygen-js/comments{/number}","issue_comment_url":"https://api.github.com/repos/urbit/keygen-js/issues/comments{/number}","contents_url":"https://api.github.com/repos/urbit/keygen-js/contents/{+path}","compare_url":"https://api.github.com/repos/urbit/keygen-js/compare/{base}...{head}","merges_url":"https://api.github.com/repos/urbit/keygen-js/merges","archive_url":"https://api.github.com/repos/urbit/keygen-js/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/urbit/keygen-js/downloads","issues_url":"https://api.github.com/repos/urbit/keygen-js/issues{/number}","pulls_url":"https://api.github.com/repos/urbit/keygen-js/pulls{/number}","milestones_url":"https://api.github.com/repos/urbit/keygen-js/milestones{/number}","notifications_url":"https://api.github.com/repos/urbit/keygen-js/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/urbit/keygen-js/labels{/name}","releases_url":"https://api.github.com/repos/urbit/keygen-js/releases{/id}","deployments_url":"https://api.github.com/repos/urbit/keygen-js/deployments","created_at":"2018-08-01T12:09:56Z","updated_at":"2018-10-24T06:18:03Z","pushed_at":"2018-10-25T17:21:53Z","git_url":"git://github.com/urbit/keygen-js.git","ssh_url":"git@github.com:urbit/keygen-js.git","clone_url":"https://github.com/urbit/keygen-js.git","svn_url":"https://github.com/urbit/keygen-js","homepage":"","size":1690,"stargazers_count":2,"watchers_count":2,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":2,"mirror_url":null,"archived":false,"open_issues_count":6,"license":null,"forks":2,"open_issues":6,"watchers":2,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/urbit/keygen-js/pulls/34"},"html":{"href":"https://github.com/urbit/keygen-js/pull/34"},"issue":{"href":"https://api.github.com/repos/urbit/keygen-js/issues/34"},"comments":{"href":"https://api.github.com/repos/urbit/keygen-js/issues/34/comments"},"review_comments":{"href":"https://api.github.com/repos/urbit/keygen-js/pulls/34/comments"},"review_comment":{"href":"https://api.github.com/repos/urbit/keygen-js/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/urbit/keygen-js/pulls/34/commits"},"statuses":{"href":"https://api.github.com/repos/urbit/keygen-js/statuses/9871e7bdcef1610a0cd739ec91718591657ce91d"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"Fang-"}}