Cosmos uses ed25519 public keys at identifiers for both accounts and nodes in gaia.
Keys and addresses should have a prefixes to different different types of objects.
Interactions with the CLI should check the address/pubkey prefix before executing actions like send to only account and bond only to nodes.
by nodes here you mean validator candidates ?
Can you clarify if you're asking for a protocol change or if this can just be handled more intelligently by clients?
No protocol change. Just how we display keys and addresses.
If the key describes a node, it should a prefix NPK and maybe CPK or something like that.
Zcash does a great job of prepending specific bytes when a key is serialized.
So what I am saying is that we should make rules about expected prefixs when keys are serialized and deserialized in different contexts.
Would you mind writing a concrete proposal for how to do this, covering different address types and the check sum encoding ?
Will do
I'm proposing that we use a variant of Bech32 called Bech32Cosmos.
The only difference from BIP173 is that we change the seperator character between from "1" to ":". I've looked at some bech implementations and this a super simple change.
Bech32 address consist of case insensitive ascii characters. Before decoding, the cases of all characters are lowercased.
Here is what I think a Cosmo Hub Address looks like in Bech32Cosmos
COHUBACC:1hhqxhhtwgxt5tk88hn6snzy8xykqwsc2uns5wp
COHUBNDE:1hhqxhhtwgxt5tk88hn6snzy8xykqwsc2uns5wp
or a photon zone
COPHTACC:1hhqxhhtwgxt5tk88hn6snzy8xykqwsc2uns5wp
An IBC transactions can then send from
COPHTACC:1hhqxhhtwgxt5tk88hn6snzy8xykqwsc2uns5wp
to
COHUBACC:1hhqxhhtwgxt5tk88hn6snzy8xykqwsc2uns5wp
and clients will know how to route that.
Awesome. So, we are using : afterall?
I'll add some more explanation of the above addresses.
COHUBNDE:1hhqxhhtwgxt5tk88hn6snzy8xykqwsc2uns5wp
The first two letters CO designate it as an address within Cosmos.
The next three letters show the source zone HUB.
The next three letters show the type of account. ACC is a user account, that can send and receive normal token transfers. NDE is a node account, which is used for delegation transactions.
Zones that share the validator set with the hub just reject transaction send to NDE on them.
Generally, the client software should display the addresses like this:
CO-HUB-ACC : 1hhqxhhtwgxt5tk88hn6snzy8xykqwsc2uns5wp
So the address format is:
[2 characters to designate the network][3 characters to designate the zone][ACC || NDE to designate the type of account][20 characters derived from the public key on the zones]
Current implementation in SDK2 branch is as follows:
I think this is the implementation we need. Not including the type of the PubKey allows us to blind addresses... wouldn't it be better to not know what type an address is, until it needs to be known by revealing the pubkey in the first transaction which uses the account? I don't see a benefit to including the type to the address.
So here is what I'm suggesting
I'm suggesting that we do Bech32Cosmos(Go-Wire(key || address)) for human interactable output.
Bech32Cosmos adds context, routing information, and error correction to protect against unintentional loss of funds. The lack of these features in other blockchains has led to an ongoing leakage due to user confusion or error.
go-wire already includes 4 prefix bytes as the canonical representation of the pubkey, depending on the concrete type. bech32 is incompatible with the way go-wire works.
In go-wire binary, all pubkeys/privkeys/signatures have 4 prefix bytes (which are not the same for a given algorithm such as Ed25519) prepended, whether or not they are the value of a PubKey interface or straight up PubKeyEd25519.
The UI for now can just read the first 4 bytes and switch accordingly.
A PubKey doesn't need to be as short as an address... so I think it would be fine to use a simple checksum protocol... Pubkey{<PubKeyHexBytes>:<ChecksumHexBytes>}, for example.
We can use bech32 but I wouldn't want to include the pubkey algo type... just the go-wire bytes, which would include the 4byte prefix.
csmsaddr:1hhqxhhtwgxt5tk88hn6snzy8xykqwsc2uns5wp
csmspub: tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7
csmspriv: tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7
Addresses, pubkeys, and private keys should be re-usable across zones... so none of this should encode a zone...
bech32 is incompatible with the way go-wire works.
I don't understand this.
Don't we only need to bech32 encode addresses? I agree there's no need to include the type (secp vs ed25519) in there.
I see no reason why we couldnt encode the output of go-wire as well, just much less of a need, since the primary concern is users sending to invalid addresses.
Finally, it seems useful to differentiate validator addresses from account addresses. I don't like using "node" to refer to that. so maybe csmsaddr: and csmsval: would be the respective prefixes
On another note, @tarcieri has recommended we don't use ripemd. I suspect ripemd160(sha256()) is ok, but for the ed25519 addresses maybe we should do the same or consider a different non-nist hashing function, perhaps BLAKE2
Also we use ripemd160 everywhere we take hashes (ie. all the merkle trees). Maybe something we should evaluate
There's nothing wrong with RIPEMD160 per se, other than its birthday bound at 2^80, where best practice would suggest 2^128 at a minimum across-the-board. 2^80 is within reach of a practical (albeit very expensive) attack.
SHA-512/256 and BLAKE2b are the fastest on 64-bit architectures. SHA-256 will be the fastest whenever the Intel SHA extensions ship (probably some time in 2019 at this rate).
I'm built the go code for our proposed variant of bech32.
https://github.com/cosmos/bech32cosmos
I'm going to try and integrate it into the gaia/sdk
@tarcieri Birthday attack ~ 2^80 seems OK (it's not a preimage attack), but what's the quantum resistance of these algos looking like?
@zmanian I checked out your PR. This isn't a comment on the Bech32Cosmos spec itself. I think it's fine to discuss the format, and I think in general "
With the above assumption, I don't think the Bech32Cosmos spec should affect any implementation of go-crypto. Go-Crypto provides raw bytes forms for Address/PubKey/PrivKey/Signature. I think these raw bytes then need to be Bech32Cosmos encoded, but that happens by the caller of go-crypto/go-wire, not by go-crypto/go-wire itself. Go-crypto/go-wire can be armor-agnostic.
In terms of our code, we could do something like:
type B32CMessage string
func (_ B32CMessage) Info() string {...} // Info part
func (_ B32CMessage) Bytes() []byte {...} // Data part
func (_ B32CMessage) Decode(cdc *wire.Codec, ptr interface{}) (err error) // uses go-wire to decode bytes
func (_ B32CMessage) DecodeAddress() (info string, addr Address, err error) // convenience, uses go-crypto to decode Address
func (_ B32CMessage) DecodePubKey() (info string, pub PubKey, err error) // convenience, uses go-crypto to decode PubKey
...
And genesis.json can be composed of B32CMessage objects, which we can convert to whatever easily.
There are other ways too... we can always implement other convenience functions with various method signatures. They could be exposed in go-crypto or the cosmos-sdk.
@jaekwon my understanding is there aren't (known) efficient quantum algorithms to find collisions, so collision resistance remains at 2^80 post-quantum. However, Grover would reduce the preimage resistance to 2^80.
Hash functions that have a 256-bit digest have a 128-bit birthday bound and 128-bit post-quantum preimage resistance, which is why they're generally recommended these days.
In the Cosmos SDK, Msg sign bytes are composed into a total Tx sign bytes, and that's composed of the ChainID and MySequence as well, which is ultimately signed. That isn't the same as the Msg binary bytes, which is more efficient for transfer, but doesn't provide enough context for a general-purpose signer to display information to the user in a safe/simple way (whereas JSON does e.g. because it includes key names, among other reasons). Anyways, with that in mind,
I definitely like the idea of including the base32 as a string in the JSON Msg bytes to sign, so the user can inspect this address from the JSON structure from any signing device. But what about the human-readable part, and the checksum? Should we strip those from the JSON Msg sign bytes?
First question: If we include the human readable part in the JSON Msg sign bytes, then they can appear in the ledger. We don't need to include the checksum in the sign bytes if the ledger can compute it dynamically (and why not support checksums for all JSON types? Another discussion for another day.)
Second question: If we strip anything from the JSON Msg sign bytes, do we also strip them from the Tx/Msg bytes entirely? If so, then the human readable part becomes private information only for the wallet. Maybe it makes sense to include the whole human-readable part for now, with the intent to extend the left-hand-side in the future with private information (e.g. it could be in parentheses, like (a gift)cosmosaddr:1042qqdwojdj.... What about the checksum?
If it's in the Tx bytes then it must be in the sign bytes, otherwise it becomes a bad malleability issue.
It is somewhat pointless to sign over the checksums because checksums are purely a human affordance.
It is somewhat pointless to sign over the checksums because checksums are purely a human affordance.
Though we're currently designing the sign bytes to be human readable for transactions (ie. canonical JSON) so in that case I'd think we want the bech32 in there
This has been taken care of by #1109 right? (Though using normal bech32 instead of the different literal)
yup
Most helpful comment
We can use bech32 but I wouldn't want to include the pubkey algo type... just the go-wire bytes, which would include the 4byte prefix.
csmsaddr:1hhqxhhtwgxt5tk88hn6snzy8xykqwsc2uns5wp
csmspub: tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7
csmspriv: tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7
Addresses, pubkeys, and private keys should be re-usable across zones... so none of this should encode a zone...