stacks-archive / blockstack-browser

The Blockstack Browser
Mozilla Public License 2.0
1.12k stars 199 forks source link

Integrate a BIP44-compliant & multi-device HD keychain system #330

Closed shea256 closed 7 years ago

shea256 commented 7 years ago

Requirements:

shea256 commented 7 years ago

We already worked out the HD paths here: https://github.com/blockstack/blockstack-portal/issues/131

shea256 commented 7 years ago

See this for the keychain model: https://forum.blockstack.org/t/blockstack-keychain-model-lets-discuss/726

shea256 commented 7 years ago

Device keychain --> identity branch --> name account --> owner, signing, encryption keychains

m / 888' / 0' / 0' / 0 = name owner
m / 888' / 0' / 0' / 1 = name admin
m / 888' / 0' / 0' / 2 = signature verifier
m / 888' / 0' / 0' / 3 = data encrypter
m / 888' / 0' / 0' / 0' = app keychain
shea256 commented 7 years ago

@larrysalibra Does this look good to you?

shea256 commented 7 years ago

Keychain model visual:

12bfb84bbf1efc0e957da0995a342a3418e94638

shea256 commented 7 years ago

For bitcoin, we'll simply use the following:

Later, we'll make the wallet multi-address and add the ability to have new paths:

shea256 commented 7 years ago

Some quick code I whipped up for this:

import {randomBytes} from 'crypto'
import {HDNode} from 'bitcoinjs-lib'

const masterKeychain = HDNode.fromSeedBuffer(randomBytes(32))
const masterKeychainID = masterKeychain.neutered()

const bitcoinKeychain = masterKeychain.deriveHardened(44).deriveHardened(0)
const firstBitcoinAddress = bitcoinKeychain.deriveHardened(0).derive(0)

const identityKeychain = masterKeychain.deriveHardened(888).deriveHardened(0)
const firstIdentityAddress = identityKeychain.derive(0)
larrysalibra commented 7 years ago

Awesome!

larrysalibra commented 7 years ago

Add another level to identity keychain: m / 888' / 0' / 0' / 0 m / identity / blockstack on bitcoin / name 0 / name owner

shea256 commented 7 years ago
Type Path
Name 0 - name key m / 888' / 0' / 0'
Name 0 - signer key m / 888' / 0' / 1'
Name 1 - name key m / 888' / 1' / 0'
Name 1 - verifier key m / 888' / 1' / 1'
Name 1 - encrypter key m / 888' / 1' / 2'
Name 1 - app keychain m / 888' / 1' / 3'
Name 1 - app key 0 m / 888' / 1' / 3' / app-name-hash-31'
m / blockstack on bitcoin / name 0 / name key

Special case for legacy migration:

Check m / 888' / 0' / 0' / 0 and consider it equivalent to m / 888' / 0' / 0'.

jcnelson commented 7 years ago

A couple things we'll need to store externally:

shea256 commented 7 years ago

An app key is derived as follows:

m / 888' / 1' / 3' / int-31(app-name-hash)'

Where a int-31 is the integer form of the first 31 bits of a byte sequence.

Also, the hash must include both the name and a salt.

jcnelson commented 7 years ago

To derive an application-specific key, we should:

jcnelson commented 7 years ago

To disclose which apps I use, I upload a signed manifest of all m / 888' / 1' / 3' / int-31(app-name-hash)' paired with the relevant app-name.

shea256 commented 7 years ago

OK pulling all this together:

Type Path
Name 0 - name key m / 888' / 0' / 0'
Name 0 - signer key m / 888' / 0' / 1'
Name 1 - name key m / 888' / 1' / 0'
Name 1 - verifier key m / 888' / 1' / 1'
Name 1 - encrypter key m / 888' / 1' / 2'
Name 1 - app keychain m / 888' / 1' / 3'
Name 1 - app key 0 m / 888' / 1' / 3' / app-name-hash-31'
jcnelson commented 7 years ago

Where an ICANN app-name is suffixed with .i, and a Blockstack app-name is suffixed with .x. For example, helloblockstack.com becomes helloblockstack.com.i, whereas storage-app.id becomes storage-app.id.x in the above app-name-hash-31 calculation.

larrysalibra commented 7 years ago

@jcnelson @shea256 If we're going to check two addresses for names for "legacy" names, which means twice the number of api calls to lookup names at an address, two code paths and support this forever, why don't we do this instead?

Type Path
Name 0 - name key m / 888' / 0' / 0' / 0
Name 0 - signer key m / 888' / 0' / 1'
Name 1 - name key m / 888' / 1' / 0' / 0
Name 1 - verifier key m / 888' / 1' / 1'
Name 1 - encrypter key m / 888' / 1' / 2'
Name 1 - app keychain m / 888' / 1' / 3'
Name 1 - app key 0 m / 888' / 1' / 3' / int-31(app-name-hash)'
m / blockstack on bitcoin / name 0 / 0' / name key

With this method we don't have any special cases and only need to do 1 address -> names look up per name index.

larrysalibra commented 7 years ago

From slack:

ryanshea [11:01 AM] @larrysalibra @jcnelson wait can one of you remind me why this is a problem again? https://github.com/blockstack/blockstack-browser/issues/330#issuecomment-302905902 GitHub integrate a BIP44-compliant HD keychain system · Issue #330 · blockstack/blockstack-browser blockstack-browser - The Blockstack Browser Portal

[11:01] this model that we put together earlier ^

jude [11:02 AM] if you have unhardened children, then if you lose one key, you lose all of them

ryanshea [11:02 AM] ok yes but I actually don't think this is a problem

jude [11:03 AM] why risk it?

ryanshea [11:03 AM] ok here's why

[11:03] you can give out m / 888' / 0' / 0' as your name keychain

[11:03] you just give out a single key

[11:04] and then for that given name, another party knows how to verify your signature and at the same time encrypt something for that name

[11:04] it's a single key tree that needs to be handed out

[11:04] for apps we'll rely on app-specific keychains (edited)

[11:05] but there are cases where fore example with daniel, we just wanted to use the root name key

[11:05] another thing is the name key needs to delegate to child keys in some way

[11:05] so either that key itself is going to be signing delegations

[11:06] or a provably related key is going to need to sign delegations (edited)

jude [11:06 AM] I thought we agreed that the user is going to have to store a signed {app-name: pubkey} mapping anyway, so why not simply do so for the name/owner/admin/whatever keys as well?

[11:06] since app public keys are hardened children

[11:06] (and that's unavoidable)

ryanshea [11:07 AM] yeah that's an option

[11:07] I'm actually thinking of a third option right now

[11:07] that's even simpler

[11:07]

m / 888' / 0' / 0' / 0' = app keychain```

[11:08] 
where the name owner does key delegation

[11:08] 
it's the default "key delegation action" signing key

jude [11:08 AM] 
also, recall that the key paths below were agreed to because we don't want to force all the early adopters to re-update their zone files:
```Name 0 - name key    m / 888' / 0' / 0' / 0
Name 0 - signer key    m / 888' / 0' / 1'
Name 1 - name key    m / 888' / 1' / 0' / 0
Name 1 - verifier key    m / 888' / 1' / 1'
Name 1 - encrypter key    m / 888' / 1' / 2'
Name 1 - app keychain    m / 888' / 1' / 3'
Name 1 - app key 0    m / 888' / 1' / 3' / int-31(app-name-hash)'

ryanshea [11:08 AM] what I posed above would accomplish the same thing

[11:09] the last thing larry proposed is unfortunately messy

[11:09] I also realized the name owner key will have to do signing anyway

[11:09] so the signer/verifier key might not even make sense

jude [11:10 AM] um...I thought it was recommended by NIST to have wholly-separate signing/verifying keys from owner ("master") keys?

ryanshea [11:10 AM] yes, key signing keys and payload signing keys (edited)

[11:10] however the key signing key needs to sign the payload signing key

[11:10] so it is doing signing, just at a much lower frequency

[11:11] the reason here is so that the key signing key never has to be rotated (or at least infrequently)

[11:12] so the key signing key can sign a key in the app keychain, that's one option

jude [11:16 AM] let me see if I understand this correctly: Name 0's key paths are:

m / 888' / 0' / 0' / 0' = app keychain root
m / 888' / 0' / 0' / 0' / int-31(hash(foo-app))' = key for app with name "foo-app"
m / 888' / 0' / 0' / 1' = signing/verifying key
m / 888' / 0' / 0' / 2' = encryption key 

Name 1's key paths are

m / 888' / 1' / 0' / 0' = app keychain root
m / 888' / 1' / 0' / 0' / int-31(hash(bar-app))' = key for app with name "bar-app"
m / 888' / 1' / 0' / 1' = signing/verifying key
m / 888' / 1' / 0' / 2' = encryption key

(edited)

[11:17] @larrysalibra will have to chime in to clarify whether or not this is compatible with the code that is deployed today

[11:21] more generically: Name n's key paths for version v of this spec are:

m / 888' / ${n}' / ${v}' / 0' = app keychain root
m / 888' / ${n}' / ${v}' / 0' / int-31(hash(name)) = app-specific key
m / 888' / ${n}' / ${v}' / 1' = signing/verifying key 
m / 888' / ${n}' / ${v}' / 2' = encryption key 

Also, note that if we go with this scheme, the user still needs to explicitly disclose their public signing and encryption keys, since they are cryptographically unrelated to m

ryanshea [11:22 AM] hmm

[11:23] I'm thinking this over

jude [11:23 AM] that's unavoidable either way since anything beneath m / 888' will be underivable from m without disclosure

[11:24] moreover, the user needs to disclose their app pubkeys either way

[11:25] so, no matter what we do, we're still going to have to store key data in addition to the profile (possibly embedded within the profile)

ryanshea [11:26 AM] yes I was trying to see if we could store a single xpub

[11:26] that was one of the motivations behind this

[11:26]

m / 888' / 0' / 0' / 1 = name admin
m / 888' / 0' / 0' / 2 = signature verifier
m / 888' / 0' / 0' / 3 = data encrypter
m / 888' / 0' / 0' / 0' = app keychain

jude [11:27 AM] the whole point of having hardened children is to make it so you can't derive public keys from your master public key

ryanshea [11:27 AM] yes but perhaps you want to allow that

[11:27] in this case we want someone to derive keys / 0 through / 3

[11:27] and the important thing is that m / 888' / 0' / 0' is what is to be protected (edited)

[11:28] think of them as role variants of the same key

jude [11:28 AM] except they really are the same key

[11:28] if I have one unhardened child, I have all of them

[11:28] the search space of 2^31 won't stop me from discovering literally all of your derived keys

ryanshea [11:29 AM] there are only 4 derived keys here

[11:29] all for of which I want you to know

[11:29] as in, I give you this m / 888' / 0' / 0'

[11:29] and then I'm like "verify my message"

[11:29] and you derive m / 888' / 0' / 0' / 2

larry [11:30 AM] catching up

jude [11:31 AM] yes, I remember having this conversation. The point I made then was that there are some unknown-unknowns here, such as the possible existence of a related-key-like attack whereby I might be able to crack your private key using signatures from your derived children

ryanshea [11:31 AM] yes right ok

jude [11:31 AM] the existence of the relationship between your unhardened children may lead to future cryptographic breaks

[11:31] but we also all agreed that none of us have the mathematics background to go reason about this effectively--it's entirely possible that there is no risk (but I can't prove it either way) (edited)

[11:33] I also want to remind you that since we've already agreed that app-specific keys are hardened children, we already find ourselves needing to store key data alongside the profile no matter how we derive signing/encryption/owner keys

[11:33] so, there's no point to making owner/encryption/signing keys unhardened--there's no upside to be had, but there is a potential downside (edited)

larry [11:33 AM] if they really are the same key because they're all derivable, then we should explicitly use the same key https://blockstack.slack.com/archives/C074LC7RC/p1496330909678352 jude except they really are the same key Posted in #engineeringToday at 11:28 AM

[11:37] another option is to move the non-name owner keys to arbitrary locations and expose both the derivation path and the public key in the zonefile or profile.

[11:37] this would allow users to rotate keys

[11:37] and they could use the derivation path to derive the private key (edited)

[11:38] otherwise, if i transfer my name to a new address, all of the other keys also become invalid...i have to re-encrypt all of my data as part of the transfer...right? (edited)

aaronb [11:40 AM] Wait-- I think this needs clarification-- derivation of child keys is done with the parent pubkey, a (random) chain code, and an index as input, meaning that if there was a "related key attack" that attack would work for the public key on its own jude the existence of the relationship between your unhardened children may lead to future cryptographic breaks Posted in #engineeringToday at 11:31 AM

jude [11:41 AM]

Wait-- I think this needs clarification-- derivation of child keys is done with the parent pubkey, a (random) chain code, and an index as input, meaning that if there was a "related key attack" that attack would work for the public key on its own the chaincode would be publicly known

aaronb [11:41 AM] but that doesn't matter if you're only worried about exposing the parent private key

larry [11:41 AM] didn't we find out that the chain code is always "bitcoin seed" in bitcoinjs-lib?

jude [11:41 AM] yup

[11:42]

but that doesn't matter if you're only worried about exposing the parent private key what I'm worried about is that an attacker might be able to use the signatures from the set of unhardened children to break the parent private key. The attacker would have more signatures (and nonces) to work with than they would have if the keys were truly separate (edited)

aaronb [11:43 AM] ah, I see

larry [11:43 AM] that makes sense

jude [11:43 AM] I'm not sure how (or if) the attack would work, however. This is why I call it an unknown-unknown

larry [11:44 AM] i can imagine situations where you want one of your keys to change (such as the name owner key) but don't want others to change (such as the encryptor key)

[11:45] in that case it seems like we shouldn't use fixed paths to derive these other keys because we wouldn't be able to change 1 without changing all of them

ryanshea [11:46 AM] why would you rotate your name owner key?

larry [11:47 AM] because i restored my wallet in a public airport and i dont trust the confidentiality of the mnemonic anymore, so want to move my names to another wallet. i want to move one name to another wallet so that I can use it separately from some others.

[11:47] maybe one name is work related

jude [11:49 AM] the name owner key is in the blockain, but all other keys can be put into the zonefile. So, we could simply allow the user to pick the derivation path just as long as they don't reuse paths

ryanshea [11:49 AM] yeah but then you'd just get a new mnemonic

[11:49] and start the whole keychain over

[11:50] and use a migration tool built in to the browser

jude [11:50 AM] then how about this? put the signer and encryption public keys in the zone file and we use a random derivation path from m each time the user wants to update any of them? (edited)

[11:51] requires a NAME_UPDATE though

ryanshea [11:51 AM] that's not great for importing

[11:51] then you need to store the random state

[11:51] the entire reason for deterministic paths is so that you can switch devices with just the 12 word phrase

larry [11:51 AM] yes

ryanshea [11:51 AM] paths can be scanned

[11:51] state can be restored

jude [11:52 AM] hold on--I thought m was going to be specific to the device?

[11:52] so, when does a user ever import m?

ryanshea [11:52 AM] in the long term it will be

[11:53] but even then, let's say you want to switch browsers

[11:53] or let's say your state got lost and you want to restore it

[11:53] on the same device

larry [11:53 AM] where will the relationships between devices be stored?

[11:53] zone file?

jude [11:53 AM] that's what I was planning on

ryanshea [11:53 AM] yeah but the zone file only applies to a single name

[11:54] so you can't rely on that

[11:54] let's say I have 3 devices and 10 names

[11:54] unless you do it on a per name basis

[11:54] copay lets you have different arrangements

jude [11:55 AM] was thinking that was going to be the case

[11:55] my "work" name identifies my "work" devices (edited)

ryanshea [11:55 AM] yeah that could work

[11:55] will just be a challenge to get it as simple as possible

jude [11:56 AM] no matter what we do, we're heading to a world where names are controllable only from certain devices anyway

[11:56] Gaia needs to know which devices can write to datastores under a specific name, so we're going to need to let it know which devices are linked to a name already

larry [11:58 AM] it sounds like we need to figure out the multi device story sooner rather than later

[11:58] from the user's perspective, i expect to be able to use my name from all of my devices. i expect that if i lose my phone, i can still use my name from my laptopp

[11:59] and that there's some way to revoke my phone

ryanshea [11:59 AM] yes exactly

jude [11:59 AM] so, each device gets a separate m

ryanshea [11:59 AM] short term I think the solution is load your phrase onto multiple computers and then add in hardware keys

[11:59] simple enough model

jude [11:59 AM] and maybe something like b58check_encode(ripemd160(double_sha256(pubkey(m / 888')))) is the device ID (edited)

larry [11:59 AM] i expect that given a backup phrase, i can restore one device and then also use that device to re-provision other devices

ryanshea [11:59 AM] long term is each device gets a keychain

[11:59] and you pair devices

[12:00] pairing for each name would be nice, but I see that as an advanced feature

[12:00] way too complex to ask a user to do that

larry [12:00 PM] how do the hardware keys interact with our current keychain?

jude [12:03 PM] maybe the user could have a master key that derives each m as a hardened child?

[12:04] more generically, derive m for a given device using a "master" mnemonic. Doesn't have to be bip32 per se

[12:04] maybe it could be m = master / int-31(hash(user's chosen device name))'

ryanshea [12:05 PM] with multi-device pairing I'd actually generate completely separate keys, and if you can't do that because of short term implementation complexity, I'd just focus on a single keychain copied across all devices

[12:05] we should look into how complex it would be to implement device pairing

[12:05] and make an assessment on how soon we should tackle that

larry [12:06 PM] have either of you looked at how keybase does device pairing?

ryanshea [12:06 PM] yeah it's nice

jude [12:08 PM] see, this is why I thought using the zone file to identify devices was a good idea

[12:08] as long as you control the name owner key for that name, you can add and revoke devices at will

[12:09] the name owner key itself would have to have a mnemonic representation, so you could recover it on any device (possibly a brand-new one) and use it to re-key

[12:12] and we already do this, since we back up your encrypted m when you log into Portal for the first time (edited)

[12:14] related, I think it's generally a good idea to keep the derivation specifics isolated within Portal, and isolated to the key owner. Other users and other subsystems should not care how you derive your public keys; they should only care about being able to get them

[12:16] Gaia shouldn't have to know or care about the derivation steps you used; Gaia only has to find your app-specific public keys (which can be delivered in a signed JSON blob) and find your signing key (which should be listed in your zonefile anyway)

aikon3390 [12:19 PM] joined #engineering

jude [12:22 PM] so, how about this:

fletcher [12:26 PM] joined #engineering

muneeb [12:26 PM]

I’d just focus on a single keychain copied across all devices

[12:27] can be a security issue even in the short run

[12:27] lose 1 device and everything can be compromised

jude [12:27 PM] moreover, in something like Qubes OS, you'd have a m for each container

jcnelson commented 7 years ago

Background on the weaknesses of unhardened (extended) keys: https://bitcoin.org/en/developer-guide#hardened-keys

shea256 commented 7 years ago

Good point @jcnelson.

Thinking about multi-device derivation now.

Can we use a "privacy derivation seed" for allowing multiple devices to derive each other's children?

This is comparable to the privacy key vs spending key model in the stealth address system.

larrysalibra commented 7 years ago

This is the keychain derivation scheme we came up with:

It is backwards compatible with the current (0.9.0 & higher) browser wallet format.

Adding a device requires the secrets in red which are generated from the master secret m. After adding devices, users will be prompted to "lockdown" their device which removes the red secrets and leaves only the device specific secret k

blockstack browser wallet

There exists a key delegation file which contains a signed bundle of public keys so that people can verify that keys belong to the right person.

@jcnelson can you add documentation on the key delegation file? tagging @shea256

larrysalibra commented 7 years ago

Gaia is dependent on this.

shea256 commented 7 years ago

I don't think we should do it this way @larrysalibra @jcnelson. Each device should have it's own independent m.

shea256 commented 7 years ago

Also keychain secrets shouldn't be shared across devices.

Also if you register a new name, you shouldn't have to repair the other device.

kantai commented 7 years ago

From my understanding, I think the delegation file is an important part of the design that solves the problem you're raising, @shea256. The delegation file is signed by the owner key, which then lists keys like the device-specific identity public key.

To register a name, the client that registers the name is able to construct m/888'/2'/2, which allows it sign a new delegation file that delegates to the existing device keys. So when a registration is successful, you won't have to repair the other devices.

This design would depend on the delegation system, though.

jcnelson commented 7 years ago

There needs to be a standard way for discovering public keys. In particular, we need to:

This will entail creating a "key delegate" JWT that lists the set of signing keys for a user. The key m/888'/${n}'/${n} is the "owner" key for the nth name, and the key m/889'/${d}'/${n}'/${n}' is the "signing" key for the nth name on the dth device. The key delegate JWT is the set of keys m/889'/${d}'/${n}/${n} for all d, signed by m/888'/${n}'/${n}.

The key delegate JWT will be stored alongside the profile. The profile is signed with either the identity key m/888'/${n}'/${n}, or any of the keys in the delegate JWT. In the latter case, the profile validation protocol is to load the profile (but without trusting it), load the delegate JWT it points to, and then verify that (1) the delegate file is signed by m/888'/${n}'/${n}, and (2) at least one of the keys in the delegate file signed the profile.

The point of the above is to ensure that you can update your profile from any device without needing m or your main identity key m/888'/${n}'/${n}. Instead, you'd use your device-specific identity key, which anyone can verify corresponds to your main identity key by looking up and verifying your delegate file. The only time you need m is to add or remove device-specific keys from the delegate file.

larrysalibra commented 7 years ago

Also keychain secrets shouldn't be shared across devices.

When you add a new device, you only need to enter the device specific k from the device that has m. That device at the same time updates the delegation file with the public key of the new device's k.

If none of your devices have m (and the ability to derive the keys in red in the diagram), you would have to restore m to that device by entering the 12 word mnemonic on that device. Doing so would make it so that device can issue name related transactions and add or remove devices.

kantai commented 7 years ago

Figure of keychains across multiple devices:

https://raw.githubusercontent.com/blockstack/blockstack-browser/kantai-patch-1/docs/keychain.png

The red nodes in that figure participate in a "2 of 3" multisig for name operations and signing of the key delegate object (which delegates power to the device keys).

The blue nodes in that figure are the children keys used for signing, encryption, and application data.

shea256 commented 7 years ago

Interesting discovery: JWTs can have multiple signatures.

From the spec (http://self-issued.info/docs/draft-jones-json-web-token-01.html):

  1. JWT Serialization Formats JSON Web Tokens (JWTs) support two serialization formats: the JWT Compact Serialization, which is more space efficient and intended for uses where the token is passed as a simple string-valued parameter, and the JWT JSON Serialization, which is more general, being able to contain multiple signatures over the same content. The two serialization formats are intended for use in different contexts.

Here's an example they include:

{"header":[
  "eyJhbGciOiJSUzI1NiJ9",
  "eyJhbGciOiJFUzI1NiJ9"],
 "payload":"eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
 "signature":[
  "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw",
  "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"]
}
shea256 commented 7 years ago

key-delegation-1

Thinking about a model like this. Here, the name owner key is used to sign delegations to the device keychains.

shea256 commented 7 years ago

I just ran an experiment and updated jsontokens-js so that it supports an expanded, unencoded form of JSON Web Tokens. This is making me pretty excited.

{
    "header":["eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ"],
    "payload":"{\"issuedAt\":\"1440713414.85\",\"challenge\":\"7cd9ed5e-bb0e-49ea-a323-f28bde3a0549\",\"issuer\":{\"publicKey\":\"03fdd57adec3d438ea237fe46b33ee1e016eda6b585c3e27ea66686c2ea5358479\",\"chainPath\":\"bd62885ec3f0e3838043115f4ce25eedd22cc86711803fb0c19601eeef185e39\",\"publicKeychain\":\"xpub661MyMwAqRbcFQVrQr4Q4kPjaP4JjWaf39fBVKjPdK6oGBayE46GAmKzo5UDPQdLSM9DufZiP8eauy56XNuHicBySvZp7J5wsyQVpi2axzZ\",\"blockchainid\":\"ryan\"}}",
    "signature":["DUf6Rnw6FBKv4Q3y95RX7rR6HG_L1Va96ThcIYTycOf1j_bf9WleLsOyiZ-35Qfw7FgDnW7Utvz4sNjdWOSnhQ"]
}
jcnelson commented 7 years ago

sure, wrapping a JWT within a JWT would work as a backwards-compatible solution :) Generalizes to n signatures as well.

I would also propose that we always sign in a stable order, i.e. from low-value public key x-coordinates to high-value public key x-coordinates (since in compressed keys, this is the only numeric information that is retained).

shea256 commented 7 years ago

@jcnelson Yes but to clarify, that's not a JWT inside a JWT. It's a single JWT in expanded form, but with a slight modification of keeping the payload unencoded. The JWT expanded form is expressed as an object/dictionary instead of as a string with periods in between the parts. The header and payload are each arrays so that multiple signatures can be included.

And makes sense RE signing. We can take a look at the jsontokens-js code and see how that is currently being done.

shea256 commented 7 years ago

@jcnelson @larrysalibra Question RE the keys.

In what contexts will the user use the top level signature signing and decryption keys for a given name. That is, the list signature verifying and encryption keys that aren't associated with a particular app.

Of the top of my head, I'm thinking that authentication should be done with either (a) the same key that owns the name or (b) a key that the name delegates to, which could be the top level signing key.

larrysalibra commented 7 years ago

Pasting in image from https://raw.githubusercontent.com/blockstack/blockstack-browser/kantai-patch-1/docs/keychain.png for visual reference:

keychain

In what contexts will the user use the top level signature signing and decryption keys for a given name. That is, the list signature verifying and encryption keys that aren't associated with a particular app.

If I want to send data encrypted for a certain user outside of the context of an app, I would encrypt it with the top level public encryption key. Same with signatures. For example, I want to send ryan.id an encrypted file. I don't know/care which app he uses to view it, in this case, I'd use the top-level encryption keys.

Of the top of my head, I'm thinking that authentication should be done with either (a) the same key that owns the name or (b) a key that the name delegates to, which could be the top level signing key.

In our discussions, we assumed that the top-level signing key would be used for authentication.

jcnelson commented 7 years ago

Alternatively, we could forgo any dedicated keys beneath an owner key, and say that everything is an app. If I want to send you an encrypted message, I'm going to be using an app to do so. That app can be built into Blockstack, but it's still an app and thus should still use an app-specific key.

larrysalibra commented 7 years ago

App keys will generally be generated from domain names, icann or blockstack. It seems strange to me to diverge from this convention for encryption and signing keys by using arbitrary strings (that aren't registered domain names) to generate those keys.

muneeb-ali commented 7 years ago

Catching up late on this. One quick comment:

[11:42]

but that doesn't matter if you're only worried about exposing the parent private key
what I'm worried about is that an attacker might be able to use the signatures from the set of unhardened children to break the parent private key. The attacker would have more signatures (and nonces) to work with than they would have if the keys were truly separate (edited)
aaronb [11:43 AM]
ah, I see

larry [11:43 AM]
that makes sense

jude [11:43 AM]
I'm not sure how (or if) the attack would work, however. This is why I call it an unknown-unknown

For me this is more than an unknown-unknown, I'd assume that whenever we leak additional information the attack vector increases (now the attacker has more information than the other option and can potentially use that information). Using that rule of thumb, we should avoid this.

larrysalibra commented 7 years ago

@muneeb-ali in general we should minimize the amount of information we disclose. (added to requirements)

shea256 commented 7 years ago

@larrysalibra where is the requirements document?

larrysalibra commented 7 years ago

@shea256 it's just a list of bullet points at the top of this issue. no formal doc at the moment.

feel free to add.

shea256 commented 7 years ago

Alternatively, we could forgo any dedicated keys beneath an owner key, and say that everything is an app. If I want to send you an encrypted message, I'm going to be using an app to do so. That app can be built into Blockstack, but it's still an app and thus should still use an app-specific key.

Yes exactly. This was my thinking with the diagram above. There would be the branch m/888'/0'/0' and then there's the name owner key at the "0" leaf and then every app is a hardened leaf off of that. This would include the browser portal itself. The browser portal would be considered just another app.

App keys will generally be generated from domain names, icann or blockstack. It seems strange to me to diverge from this convention for encryption and signing keys by using arbitrary strings (that aren't registered domain names) to generate those keys.

Building off of this, we could have the browser portal's "domain" or "origin" be simply "localhost". The main purpose of our app-specific keys is to separate keys by origin. Local storage is organized by origin so this is a nice corollary.

shea256 commented 7 years ago

Along the lines of keys being used for a high volume of signatures, we'll need to be able to rotate the app-specific keys as well. This could be done simply by rotating the salt.

larrysalibra commented 7 years ago

Building off of this, we could have the browser portal's "domain" or "origin" be simply "localhost". The main purpose of our app-specific keys is to separate keys by origin. Local storage is organized by origin so this is a nice corollary.

Not sure this is such a good idea in that any app running on localhost would share the same keys as the portal. Is that something we want?

Along the lines of keys being used for a high volume of signatures, we'll need to be able to rotate the app-specific keys as well. This could be done simply by rotating the salt.

Where does the salt come from?

shea256 commented 7 years ago

Not sure this is such a good idea in that any app running on localhost would share the same keys as the portal. Is that something we want?

Sorry, to clarify, it would be "localhost:8888" instead of just localhost. Only one app can run on "localhost:8888" at a time and all such apps running on "localhost:8888" will share local storage anyway.

Along the lines of keys being used for a high volume of signatures, we'll need to be able to rotate the app-specific keys as well. This could be done simply by rotating the salt.

Jude and I figured out earlier we'd need a salt when we were exploring the int32(hash(appname)) stuff.

kantai commented 7 years ago

I think for right now, it's important to spec out the paths that will ultimately require "update" transactions. App keys won't, so maybe we can fully decide on the app key paths at another time, and just go with something that works for them for now?

larrysalibra commented 7 years ago

Agree @kantai

larrysalibra commented 7 years ago

We need to encrypt the app key information to prevent others from knowing which apps you are using. To do this, we need the encrypt key to live outside of the app key hierarchy.

We plan to use a symmetric key to encrypt the app names, store the cipher text in the token file. Sharing that you are using an app with someone will involve sharing the symmetric key.

larrysalibra commented 7 years ago

I've merged the new keychain format into v0.10. Registrations from this commit on won't need to be migrated. https://github.com/blockstack/blockstack-browser/commit/920e406fc5f753ffc491cc3670410587f2f23598

larrysalibra commented 7 years ago

Don't use hashcode #538

We'll also need to create a new issue to update token file to create the key delegation bundle before we can close this out.