mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-12 04:41:22 +00:00
spec: note on byte arrays, clean up bitarrays and more, add merkle proof, add crypto.go script
This commit is contained in:
@ -15,6 +15,18 @@ Notably, every object that satisfies an interface (eg. a particular kind of p2p
|
|||||||
or a particular kind of pubkey) is registered with a global name, the hash of
|
or a particular kind of pubkey) is registered with a global name, the hash of
|
||||||
which is included in the object's encoding as the so-called "prefix bytes".
|
which is included in the object's encoding as the so-called "prefix bytes".
|
||||||
|
|
||||||
|
## Byte Arrays
|
||||||
|
|
||||||
|
The encoding of a byte array is simply the raw-bytes prefixed with the length of
|
||||||
|
the array as a `UVarint` (what Protobuf calls a `Varint`).
|
||||||
|
|
||||||
|
For details on varints, see the [protobuf
|
||||||
|
spec](https://developers.google.com/protocol-buffers/docs/encoding#varints).
|
||||||
|
|
||||||
|
For example, the byte-array `[0xA, 0xB]` would be encoded as `0x020A0B`,
|
||||||
|
while a byte-array containing 300 entires beginning with `[0xA, 0xB, ...]` would
|
||||||
|
be encoded as `0xAC020A0B...` where `0xAC02` is the UVarint encoding of 300.
|
||||||
|
|
||||||
## Public Key Cryptography
|
## Public Key Cryptography
|
||||||
|
|
||||||
Tendermint uses Amino to distinguish between different types of private keys,
|
Tendermint uses Amino to distinguish between different types of private keys,
|
||||||
@ -117,8 +129,10 @@ type PrivKeySecp256k1 [32]byte
|
|||||||
|
|
||||||
### BitArray
|
### BitArray
|
||||||
|
|
||||||
BitArray is encoded as an `int` of the number of bits, and with an array of `uint64` to encode
|
The BitArray is used in block headers and some consensus messages to signal
|
||||||
value of each array element.
|
whether or not something was done by each validator. BitArray is represented
|
||||||
|
with a struct containing the number of bits (`Bits`) and the bit-array itself
|
||||||
|
encoded in base64 (`Elems`).
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type BitArray struct {
|
type BitArray struct {
|
||||||
@ -127,8 +141,21 @@ type BitArray struct {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
This type is easily encoded directly by Amino.
|
||||||
|
|
||||||
|
Note BitArray receives a special JSON encoding in the form of `x` and `_`
|
||||||
|
representing `1` and `0`. Ie. the BitArray `10110` would be JSON encoded as
|
||||||
|
`"x_xx_"`
|
||||||
|
|
||||||
### Part
|
### Part
|
||||||
|
|
||||||
|
Part is used to break up blocks into pieces that can be gossiped in parallel
|
||||||
|
and securely verified using a Merkle tree of the parts.
|
||||||
|
|
||||||
|
Part contains the index of the part in the larger set (`Index`), the actual
|
||||||
|
underlying data of the part (`Bytes`), and a simple Merkle proof that the part is contained in
|
||||||
|
the larger set (`Proof`).
|
||||||
|
|
||||||
```go
|
```go
|
||||||
type Part struct {
|
type Part struct {
|
||||||
Index int
|
Index int
|
||||||
@ -142,14 +169,16 @@ type Part struct {
|
|||||||
Encode an object using Amino and slice it into parts.
|
Encode an object using Amino and slice it into parts.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
MakeParts(object, partSize)
|
func MakeParts(obj interface{}, partSize int) []Part
|
||||||
```
|
```
|
||||||
|
|
||||||
## Merkle Trees
|
## Merkle Trees
|
||||||
|
|
||||||
Simple Merkle trees are used in numerous places in Tendermint to compute a cryptographic digest of a data structure.
|
Simple Merkle trees are used in numerous places in Tendermint to compute a cryptographic digest of a data structure.
|
||||||
|
|
||||||
RIPEMD160 is always used as the hashing function.
|
SHA256 is always used as the hashing function.
|
||||||
|
|
||||||
|
### Simple Merkle Root
|
||||||
|
|
||||||
The function `SimpleMerkleRoot` is a simple recursive function defined as follows:
|
The function `SimpleMerkleRoot` is a simple recursive function defined as follows:
|
||||||
|
|
||||||
@ -163,16 +192,66 @@ func SimpleMerkleRoot(hashes [][]byte) []byte{
|
|||||||
default:
|
default:
|
||||||
left := SimpleMerkleRoot(hashes[:(len(hashes)+1)/2])
|
left := SimpleMerkleRoot(hashes[:(len(hashes)+1)/2])
|
||||||
right := SimpleMerkleRoot(hashes[(len(hashes)+1)/2:])
|
right := SimpleMerkleRoot(hashes[(len(hashes)+1)/2:])
|
||||||
return RIPEMD160(append(left, right))
|
return SimpleConcatHash(left, right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SimpleConcatHash(left, right []byte) []byte{
|
||||||
|
left = encodeByteSlice(left)
|
||||||
|
right = encodeByteSlice(right)
|
||||||
|
return SHA256(append(left, right))
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: we abuse notion and call `SimpleMerkleRoot` with arguments of type `struct` or type `[]struct`.
|
Note that the leaves are Amino encoded as byte-arrays (ie. simple Uvarint length
|
||||||
|
prefix) before being concatenated together and hashed.
|
||||||
|
|
||||||
|
Note: we will abuse notion and invoke `SimpleMerkleRoot` with arguments of type `struct` or type `[]struct`.
|
||||||
For `struct` arguments, we compute a `[][]byte` by sorting elements of the `struct` according to
|
For `struct` arguments, we compute a `[][]byte` by sorting elements of the `struct` according to
|
||||||
field name and then hashing them.
|
field name and then hashing them.
|
||||||
For `[]struct` arguments, we compute a `[][]byte` by hashing the individual `struct` elements.
|
For `[]struct` arguments, we compute a `[][]byte` by hashing the individual `struct` elements.
|
||||||
|
|
||||||
|
### Simple Merkle Proof
|
||||||
|
|
||||||
|
Proof that a leaf is in a Merkle tree consists of a simple structure:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
type SimpleProof struct {
|
||||||
|
Aunts [][]byte
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Which is verified using the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
func (proof SimpleProof) Verify(index, total int, leafHash, rootHash []byte) bool {
|
||||||
|
computedHash := computeHashFromAunts(index, total, leafHash, proof.Aunts)
|
||||||
|
return computedHash == rootHash
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeHashFromAunts(index, total int, leafHash []byte, innerHashes [][]byte) []byte{
|
||||||
|
assert(index < total && index >= 0 && total > 0)
|
||||||
|
|
||||||
|
if total == 1{
|
||||||
|
assert(len(proof.Aunts) == 0)
|
||||||
|
return leafHash
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(len(innerHashes) > 0)
|
||||||
|
|
||||||
|
numLeft := (total + 1) / 2
|
||||||
|
if index < numLeft {
|
||||||
|
leftHash := computeHashFromAunts(index, numLeft, leafHash, innerHashes[:len(innerHashes)-1])
|
||||||
|
assert(leftHash != nil)
|
||||||
|
return SimpleHashFromTwoHashes(leftHash, innerHashes[len(innerHashes)-1])
|
||||||
|
}
|
||||||
|
rightHash := computeHashFromAunts(index-numLeft, total-numLeft, leafHash, innerHashes[:len(innerHashes)-1])
|
||||||
|
assert(rightHash != nil)
|
||||||
|
return SimpleHashFromTwoHashes(innerHashes[len(innerHashes)-1], rightHash)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## AminoJSON
|
## AminoJSON
|
||||||
|
|
||||||
Signed messages (eg. votes, proposals) in the consensus are encoded in AminoJSON, rather than binary Amino.
|
Signed messages (eg. votes, proposals) in the consensus are encoded in AminoJSON, rather than binary Amino.
|
||||||
|
108
docs/specification/new-spec/scripts/crypto.go
Normal file
108
docs/specification/new-spec/scripts/crypto.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
crypto "github.com/tendermint/go-crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func printEd() {
|
||||||
|
priv := crypto.GenPrivKeyEd25519()
|
||||||
|
pub := priv.PubKey().(crypto.PubKeyEd25519)
|
||||||
|
sig := priv.Sign([]byte("hello")).(crypto.SignatureEd25519)
|
||||||
|
|
||||||
|
name := "tendermint/PubKeyEd25519"
|
||||||
|
length := len(pub[:])
|
||||||
|
|
||||||
|
fmt.Println("### PubKeyEd25519")
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Println("```")
|
||||||
|
fmt.Printf("// Name: %s\n", name)
|
||||||
|
fmt.Printf("// PrefixBytes: 0x%X \n", pub.Bytes()[:4])
|
||||||
|
fmt.Printf("// Length: 0x%X \n", length)
|
||||||
|
fmt.Println("// Notes: raw 32-byte Ed25519 pubkey")
|
||||||
|
fmt.Println("type PubKeyEd25519 [32]byte")
|
||||||
|
fmt.Println("```")
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Printf("For example, the 32-byte Ed25519 pubkey `%X` would be encoded as `%X`\n", pub[:], pub.Bytes())
|
||||||
|
fmt.Println("")
|
||||||
|
|
||||||
|
name = "tendermint/SignatureKeyEd25519"
|
||||||
|
length = len(sig[:])
|
||||||
|
|
||||||
|
fmt.Println("### SignatureEd25519")
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Println("```")
|
||||||
|
fmt.Printf("// Name: %s\n", name)
|
||||||
|
fmt.Printf("// PrefixBytes: 0x%X \n", sig.Bytes()[:4])
|
||||||
|
fmt.Printf("// Length: 0x%X \n", length)
|
||||||
|
fmt.Println("// Notes: raw 64-byte Ed25519 signature")
|
||||||
|
fmt.Println("type SignatureEd25519 [64]byte")
|
||||||
|
fmt.Println("```")
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Printf("For example, the 64-byte Ed25519 signature `%X` would be encoded as `%X`\n", sig[:], sig.Bytes())
|
||||||
|
fmt.Println("")
|
||||||
|
|
||||||
|
name = "tendermint/PrivKeyEd25519"
|
||||||
|
|
||||||
|
fmt.Println("### PrivKeyEd25519")
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Println("```")
|
||||||
|
fmt.Println("// Name:", name)
|
||||||
|
fmt.Println("// Notes: raw 32-byte priv key concatenated to raw 32-byte pub key")
|
||||||
|
fmt.Println("type PrivKeyEd25519 [64]byte")
|
||||||
|
fmt.Println("```")
|
||||||
|
}
|
||||||
|
|
||||||
|
func printSecp() {
|
||||||
|
priv := crypto.GenPrivKeySecp256k1()
|
||||||
|
pub := priv.PubKey().(crypto.PubKeySecp256k1)
|
||||||
|
sig := priv.Sign([]byte("hello")).(crypto.SignatureSecp256k1)
|
||||||
|
|
||||||
|
name := "tendermint/PubKeySecp256k1"
|
||||||
|
length := len(pub[:])
|
||||||
|
|
||||||
|
fmt.Println("### PubKeySecp256k1")
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Println("```")
|
||||||
|
fmt.Printf("// Name: %s\n", name)
|
||||||
|
fmt.Printf("// PrefixBytes: 0x%X \n", pub.Bytes()[:4])
|
||||||
|
fmt.Printf("// Length: 0x%X \n", length)
|
||||||
|
fmt.Println("// Notes: OpenSSL compressed pubkey prefixed with 0x02 or 0x03")
|
||||||
|
fmt.Println("type PubKeySecp256k1 [33]byte")
|
||||||
|
fmt.Println("```")
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Printf("For example, the 33-byte Secp256k1 pubkey `%X` would be encoded as `%X`\n", pub[:], pub.Bytes())
|
||||||
|
fmt.Println("")
|
||||||
|
|
||||||
|
name = "tendermint/SignatureKeySecp256k1"
|
||||||
|
|
||||||
|
fmt.Println("### SignatureSecp256k1")
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Println("```")
|
||||||
|
fmt.Printf("// Name: %s\n", name)
|
||||||
|
fmt.Printf("// PrefixBytes: 0x%X \n", sig.Bytes()[:4])
|
||||||
|
fmt.Printf("// Length: Variable\n")
|
||||||
|
fmt.Printf("// Encoding prefix: Variable\n")
|
||||||
|
fmt.Println("// Notes: raw bytes of the Secp256k1 signature")
|
||||||
|
fmt.Println("type SignatureSecp256k1 []byte")
|
||||||
|
fmt.Println("```")
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Printf("For example, the Secp256k1 signature `%X` would be encoded as `%X`\n", []byte(sig[:]), sig.Bytes())
|
||||||
|
fmt.Println("")
|
||||||
|
|
||||||
|
name = "tendermint/PrivKeySecp256k1"
|
||||||
|
|
||||||
|
fmt.Println("### PrivKeySecp256k1")
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Println("```")
|
||||||
|
fmt.Println("// Name:", name)
|
||||||
|
fmt.Println("// Notes: raw 32-byte priv key")
|
||||||
|
fmt.Println("type PrivKeySecp256k1 [32]byte")
|
||||||
|
fmt.Println("```")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
printEd()
|
||||||
|
printSecp()
|
||||||
|
}
|
Reference in New Issue
Block a user