commit
710efe576a
30
CHANGELOG.md
30
CHANGELOG.md
|
@ -1,5 +1,35 @@
|
|||
# Changelog
|
||||
|
||||
## 0.7.0
|
||||
|
||||
**May 30th, 2018**
|
||||
|
||||
BREAKING CHANGES
|
||||
|
||||
No breaking changes compared to 0.6.2, but making up for the version bump that
|
||||
should have happened in 0.6.1.
|
||||
|
||||
We also bring in the `tmlibs/merkle` package with breaking changes:
|
||||
|
||||
- change the hash function from RIPEMD160 to tmhash (first 20-bytes of SHA256)
|
||||
- remove unused funcs and unexport SimpleMap
|
||||
|
||||
FEATURES
|
||||
|
||||
- [xchacha20poly1305] New authenticated encryption module
|
||||
- [merkle] Moved in from tmlibs
|
||||
- [merkle/tmhash] New hash function: the first 20-bytes of SHA256
|
||||
|
||||
IMPROVEMENTS
|
||||
|
||||
- Remove some dead code
|
||||
- Use constant-time compare for signatures
|
||||
|
||||
BUG FIXES
|
||||
|
||||
- Fix MixEntropy weakness
|
||||
- Fix PrivKeyEd25519.Generate()
|
||||
|
||||
## 0.6.2 (April 9, 2018)
|
||||
|
||||
IMPROVEMENTS
|
||||
|
|
12
Makefile
12
Makefile
|
@ -1,9 +1,8 @@
|
|||
GOTOOLS = \
|
||||
github.com/golang/dep/cmd/dep \
|
||||
github.com/jteeuwen/go-bindata/go-bindata
|
||||
# gopkg.in/alecthomas/gometalinter.v2 \
|
||||
#
|
||||
GOTOOLS_CHECK = dep go-bindata #gometalinter.v2
|
||||
|
||||
GOTOOLS_CHECK = dep #gometalinter.v2
|
||||
|
||||
all: check get_vendor_deps build test install
|
||||
|
||||
|
@ -13,9 +12,10 @@ check: check_tools
|
|||
########################################
|
||||
### Build
|
||||
|
||||
# Command to generate the workd list (kept here for documentation purposes only):
|
||||
wordlist:
|
||||
# Generating wordlist.go...
|
||||
go-bindata -ignore ".*\.go" -o keys/words/wordlist/wordlist.go -pkg "wordlist" keys/words/wordlist/...
|
||||
# To re-generate wordlist.go run:
|
||||
# go-bindata -ignore ".*\.go" -o keys/words/wordlist/wordlist.go -pkg "wordlist" keys/words/wordlist/...
|
||||
|
||||
build: wordlist
|
||||
# Nothing else to build!
|
||||
|
@ -96,4 +96,4 @@ metalinter_all:
|
|||
# To avoid unintended conflicts with file names, always add to .PHONY
|
||||
# unless there is a reason not to.
|
||||
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
|
||||
.PHONEY: check wordlist build install check_tools get_tools update_tools get_vendor_deps test fmt metalinter metalinter_all
|
||||
.PHONEY: check build install check_tools get_tools update_tools get_vendor_deps test fmt metalinter metalinter_all
|
||||
|
|
|
@ -7,7 +7,7 @@ go-crypto is the cryptographic package adapted for Tendermint's uses
|
|||
|
||||
## Binary encoding
|
||||
|
||||
For Binary encoding, please refer to the [Tendermint encoding spec](https://github.com/tendermint/tendermint/blob/develop/docs/spec/blockchain/encoding.md).
|
||||
For Binary encoding, please refer to the [Tendermint encoding spec](https://github.com/tendermint/tendermint/blob/master/docs/spec/blockchain/encoding.md).
|
||||
|
||||
## JSON Encoding
|
||||
|
||||
|
|
294
_nano/keys.go
294
_nano/keys.go
|
@ -1,294 +0,0 @@
|
|||
package nano
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
ledger "github.com/ethanfrey/ledger"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
amino "github.com/tendermint/go-amino"
|
||||
)
|
||||
|
||||
//nolint
|
||||
const (
|
||||
NameLedgerEd25519 = "ledger-ed25519"
|
||||
TypeLedgerEd25519 = 0x10
|
||||
|
||||
// Timeout is the number of seconds to wait for a response from the ledger
|
||||
// if eg. waiting for user confirmation on button push
|
||||
Timeout = 20
|
||||
)
|
||||
|
||||
var device *ledger.Ledger
|
||||
|
||||
// getLedger gets a copy of the device, and caches it
|
||||
func getLedger() (*ledger.Ledger, error) {
|
||||
var err error
|
||||
if device == nil {
|
||||
device, err = ledger.FindLedger()
|
||||
}
|
||||
return device, err
|
||||
}
|
||||
|
||||
func signLedger(device *ledger.Ledger, msg []byte) (pub crypto.PubKey, sig crypto.Signature, err error) {
|
||||
var resp []byte
|
||||
|
||||
packets := generateSignRequests(msg)
|
||||
for _, pack := range packets {
|
||||
resp, err = device.Exchange(pack, Timeout)
|
||||
if err != nil {
|
||||
return pub, sig, err
|
||||
}
|
||||
}
|
||||
|
||||
// the last call is the result we want and needs to be parsed
|
||||
key, bsig, err := parseDigest(resp)
|
||||
if err != nil {
|
||||
return pub, sig, err
|
||||
}
|
||||
|
||||
var b [32]byte
|
||||
copy(b[:], key)
|
||||
return PubKeyLedgerEd25519FromBytes(b), crypto.SignatureEd25519FromBytes(bsig), nil
|
||||
}
|
||||
|
||||
// PrivKeyLedgerEd25519 implements PrivKey, calling the ledger nano
|
||||
// we cache the PubKey from the first call to use it later
|
||||
type PrivKeyLedgerEd25519 struct {
|
||||
// PubKey should be private, but we want to encode it via go-amino
|
||||
// so we can view the address later, even without having the ledger
|
||||
// attached
|
||||
CachedPubKey crypto.PubKey
|
||||
}
|
||||
|
||||
// NewPrivKeyLedgerEd25519 will generate a new key and store the
|
||||
// public key for later use.
|
||||
func NewPrivKeyLedgerEd25519() (crypto.PrivKey, error) {
|
||||
var pk PrivKeyLedgerEd25519
|
||||
// getPubKey will cache the pubkey for later use,
|
||||
// this allows us to return an error early if the ledger
|
||||
// is not plugged in
|
||||
_, err := pk.getPubKey()
|
||||
return pk.Wrap(), err
|
||||
}
|
||||
|
||||
// ValidateKey allows us to verify the sanity of a key
|
||||
// after loading it from disk
|
||||
func (pk *PrivKeyLedgerEd25519) ValidateKey() error {
|
||||
// getPubKey will return an error if the ledger is not
|
||||
// properly set up...
|
||||
pub, err := pk.forceGetPubKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// verify this matches cached address
|
||||
if !pub.Equals(pk.CachedPubKey) {
|
||||
return errors.New("ledger doesn't match cached key")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertIsPrivKeyInner fulfils PrivKey Interface
|
||||
func (pk *PrivKeyLedgerEd25519) AssertIsPrivKeyInner() {}
|
||||
|
||||
// Bytes fulfils PrivKey Interface - but it stores the cached pubkey so we can verify
|
||||
// the same key when we reconnect to a ledger
|
||||
func (pk *PrivKeyLedgerEd25519) Bytes() []byte {
|
||||
return amino.BinaryBytes(pk.Wrap())
|
||||
}
|
||||
|
||||
// Sign calls the ledger and stores the PubKey for future use
|
||||
//
|
||||
// XXX/TODO: panics if there is an error communicating with the ledger.
|
||||
//
|
||||
// Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes,
|
||||
// returning an error, so this should only trigger if the privkey is held
|
||||
// in memory for a while before use.
|
||||
func (pk *PrivKeyLedgerEd25519) Sign(msg []byte) crypto.Signature {
|
||||
// oh, I wish there was better error handling
|
||||
dev, err := getLedger()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
pub, sig, err := signLedger(dev, msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// if we have no pubkey yet, store it for future queries
|
||||
if pk.CachedPubKey.Empty() {
|
||||
pk.CachedPubKey = pub
|
||||
} else if !pk.CachedPubKey.Equals(pub) {
|
||||
panic("signed with a different key than stored")
|
||||
}
|
||||
return sig
|
||||
}
|
||||
|
||||
// PubKey returns the stored PubKey
|
||||
// TODO: query the ledger if not there, once it is not volatile
|
||||
func (pk *PrivKeyLedgerEd25519) PubKey() crypto.PubKey {
|
||||
key, err := pk.getPubKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// getPubKey reads the pubkey from cache or from the ledger itself
|
||||
// since this involves IO, it may return an error, which is not exposed
|
||||
// in the PubKey interface, so this function allows better error handling
|
||||
func (pk *PrivKeyLedgerEd25519) getPubKey() (key crypto.PubKey, err error) {
|
||||
// if we have no pubkey, set it
|
||||
if pk.CachedPubKey.Empty() {
|
||||
pk.CachedPubKey, err = pk.forceGetPubKey()
|
||||
}
|
||||
return pk.CachedPubKey, err
|
||||
}
|
||||
|
||||
// forceGetPubKey is like getPubKey but ignores any cached key
|
||||
// and ensures we get it from the ledger itself.
|
||||
func (pk *PrivKeyLedgerEd25519) forceGetPubKey() (key crypto.PubKey, err error) {
|
||||
dev, err := getLedger()
|
||||
if err != nil {
|
||||
return key, errors.New("Can't connect to ledger device")
|
||||
}
|
||||
key, _, err = signLedger(dev, []byte{0})
|
||||
if err != nil {
|
||||
return key, errors.New("Please open cosmos app on the ledger")
|
||||
}
|
||||
return key, err
|
||||
}
|
||||
|
||||
// Equals fulfils PrivKey Interface - makes sure both keys refer to the
|
||||
// same
|
||||
func (pk *PrivKeyLedgerEd25519) Equals(other crypto.PrivKey) bool {
|
||||
if ledger, ok := other.Unwrap().(*PrivKeyLedgerEd25519); ok {
|
||||
return pk.CachedPubKey.Equals(ledger.CachedPubKey)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MockPrivKeyLedgerEd25519 behaves as the ledger, but stores a pre-packaged call-response
|
||||
// for use in test cases
|
||||
type MockPrivKeyLedgerEd25519 struct {
|
||||
Msg []byte
|
||||
Pub [KeyLength]byte
|
||||
Sig [SigLength]byte
|
||||
}
|
||||
|
||||
// NewMockKey returns
|
||||
func NewMockKey(msg, pubkey, sig string) (pk MockPrivKeyLedgerEd25519) {
|
||||
var err error
|
||||
pk.Msg, err = hex.DecodeString(msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bpk, err := hex.DecodeString(pubkey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
bsig, err := hex.DecodeString(sig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
copy(pk.Pub[:], bpk)
|
||||
copy(pk.Sig[:], bsig)
|
||||
return pk
|
||||
}
|
||||
|
||||
var _ crypto.PrivKeyInner = MockPrivKeyLedgerEd25519{}
|
||||
|
||||
// AssertIsPrivKeyInner fulfils PrivKey Interface
|
||||
func (pk MockPrivKeyLedgerEd25519) AssertIsPrivKeyInner() {}
|
||||
|
||||
// Bytes fulfils PrivKey Interface - not supported
|
||||
func (pk MockPrivKeyLedgerEd25519) Bytes() []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sign returns a real SignatureLedger, if the msg matches what we expect
|
||||
func (pk MockPrivKeyLedgerEd25519) Sign(msg []byte) crypto.Signature {
|
||||
if !bytes.Equal(pk.Msg, msg) {
|
||||
panic("Mock key is for different msg")
|
||||
}
|
||||
return crypto.SignatureEd25519(pk.Sig).Wrap()
|
||||
}
|
||||
|
||||
// PubKey returns a real PubKeyLedgerEd25519, that will verify this signature
|
||||
func (pk MockPrivKeyLedgerEd25519) PubKey() crypto.PubKey {
|
||||
return PubKeyLedgerEd25519FromBytes(pk.Pub)
|
||||
}
|
||||
|
||||
// Equals compares that two Mocks have the same data
|
||||
func (pk MockPrivKeyLedgerEd25519) Equals(other crypto.PrivKey) bool {
|
||||
if mock, ok := other.Unwrap().(MockPrivKeyLedgerEd25519); ok {
|
||||
return bytes.Equal(mock.Pub[:], pk.Pub[:]) &&
|
||||
bytes.Equal(mock.Sig[:], pk.Sig[:]) &&
|
||||
bytes.Equal(mock.Msg, pk.Msg)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
////////////////////////////////////////////
|
||||
// pubkey
|
||||
|
||||
// PubKeyLedgerEd25519 works like a normal Ed25519 except a hash before the verify bytes
|
||||
type PubKeyLedgerEd25519 struct {
|
||||
crypto.PubKeyEd25519
|
||||
}
|
||||
|
||||
// PubKeyLedgerEd25519FromBytes creates a PubKey from the raw bytes
|
||||
func PubKeyLedgerEd25519FromBytes(key [32]byte) crypto.PubKey {
|
||||
return PubKeyLedgerEd25519{crypto.PubKeyEd25519(key)}.Wrap()
|
||||
}
|
||||
|
||||
// Bytes fulfils pk Interface - no data, just type info
|
||||
func (pk PubKeyLedgerEd25519) Bytes() []byte {
|
||||
return amino.BinaryBytes(pk.Wrap())
|
||||
}
|
||||
|
||||
// VerifyBytes uses the normal Ed25519 algorithm but a sha512 hash beforehand
|
||||
func (pk PubKeyLedgerEd25519) VerifyBytes(msg []byte, sig crypto.Signature) bool {
|
||||
hmsg := hashMsg(msg)
|
||||
return pk.PubKeyEd25519.VerifyBytes(hmsg, sig)
|
||||
}
|
||||
|
||||
// Equals implements PubKey interface
|
||||
func (pk PubKeyLedgerEd25519) Equals(other crypto.PubKey) bool {
|
||||
if ledger, ok := other.Unwrap().(PubKeyLedgerEd25519); ok {
|
||||
return pk.PubKeyEd25519.Equals(ledger.PubKeyEd25519.Wrap())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/*** registration with go-data ***/
|
||||
|
||||
func init() {
|
||||
crypto.PrivKeyMapper.
|
||||
RegisterImplementation(&PrivKeyLedgerEd25519{}, NameLedgerEd25519, TypeLedgerEd25519).
|
||||
RegisterImplementation(MockPrivKeyLedgerEd25519{}, "mock-ledger", 0x11)
|
||||
|
||||
crypto.PubKeyMapper.
|
||||
RegisterImplementation(PubKeyLedgerEd25519{}, NameLedgerEd25519, TypeLedgerEd25519)
|
||||
}
|
||||
|
||||
// Wrap fulfils interface for PrivKey struct
|
||||
func (pk *PrivKeyLedgerEd25519) Wrap() crypto.PrivKey {
|
||||
return crypto.PrivKey{PrivKeyInner: pk}
|
||||
}
|
||||
|
||||
// Wrap fulfils interface for PrivKey struct
|
||||
func (pk MockPrivKeyLedgerEd25519) Wrap() crypto.PrivKey {
|
||||
return crypto.PrivKey{PrivKeyInner: pk}
|
||||
}
|
||||
|
||||
// Wrap fulfils interface for PubKey struct
|
||||
func (pk PubKeyLedgerEd25519) Wrap() crypto.PubKey {
|
||||
return crypto.PubKey{PubKeyInner: pk}
|
||||
}
|
|
@ -1,142 +0,0 @@
|
|||
package nano
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
asrt "github.com/stretchr/testify/assert"
|
||||
rqr "github.com/stretchr/testify/require"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
)
|
||||
|
||||
func TestLedgerKeys(t *testing.T) {
|
||||
assert, require := asrt.New(t), rqr.New(t)
|
||||
|
||||
cases := []struct {
|
||||
msg, pubkey, sig string
|
||||
valid bool
|
||||
}{
|
||||
0: {
|
||||
msg: "F00D",
|
||||
pubkey: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93",
|
||||
sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400",
|
||||
valid: true,
|
||||
},
|
||||
1: {
|
||||
msg: "DEADBEEF",
|
||||
pubkey: "0C45ADC887A5463F668533443C829ED13EA8E2E890C778957DC28DB9D2AD5A6C",
|
||||
sig: "00ED74EED8FDAC7988A14BF6BC222120CBAC249D569AF4C2ADABFC86B792F97DF73C4919BE4B6B0ACB53547273BF29FBF0A9E0992FFAB6CB6C9B09311FC86A00",
|
||||
valid: true,
|
||||
},
|
||||
2: {
|
||||
msg: "1234567890AA",
|
||||
pubkey: "598FC1F0C76363D14D7480736DEEF390D85863360F075792A6975EFA149FD7EA",
|
||||
sig: "59AAB7D7BDC4F936B6415DE672A8B77FA6B8B3451CD95B3A631F31F9A05DAEEE5E7E4F89B64DDEBB5F63DC042CA13B8FCB8185F82AD7FD5636FFDA6B0DC9570B",
|
||||
valid: true,
|
||||
},
|
||||
3: {
|
||||
msg: "1234432112344321",
|
||||
pubkey: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120",
|
||||
sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208",
|
||||
valid: true,
|
||||
},
|
||||
4: {
|
||||
msg: "12344321123443",
|
||||
pubkey: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120",
|
||||
sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208",
|
||||
valid: false,
|
||||
},
|
||||
5: {
|
||||
msg: "1234432112344321",
|
||||
pubkey: "459E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120",
|
||||
sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208",
|
||||
valid: false,
|
||||
},
|
||||
6: {
|
||||
msg: "1234432112344321",
|
||||
pubkey: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120",
|
||||
sig: "716B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208",
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
bmsg, err := hex.DecodeString(tc.msg)
|
||||
require.NoError(err, "%d", i)
|
||||
|
||||
priv := NewMockKey(tc.msg, tc.pubkey, tc.sig)
|
||||
pub := priv.PubKey()
|
||||
sig := priv.Sign(bmsg)
|
||||
|
||||
valid := pub.VerifyBytes(bmsg, sig)
|
||||
assert.Equal(tc.valid, valid, "%d", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRealLedger(t *testing.T) {
|
||||
assert, require := asrt.New(t), rqr.New(t)
|
||||
|
||||
if os.Getenv("WITH_LEDGER") == "" {
|
||||
t.Skip("Set WITH_LEDGER to run code on real ledger")
|
||||
}
|
||||
msg := []byte("kuhehfeohg")
|
||||
|
||||
priv, err := NewPrivKeyLedgerEd25519()
|
||||
require.Nil(err, "%+v", err)
|
||||
pub := priv.PubKey()
|
||||
sig := priv.Sign(msg)
|
||||
|
||||
valid := pub.VerifyBytes(msg, sig)
|
||||
assert.True(valid)
|
||||
|
||||
// now, let's serialize the key and make sure it still works
|
||||
bs := priv.Bytes()
|
||||
priv2, err := crypto.PrivKeyFromBytes(bs)
|
||||
require.Nil(err, "%+v", err)
|
||||
|
||||
// make sure we get the same pubkey when we load from disk
|
||||
pub2 := priv2.PubKey()
|
||||
require.Equal(pub, pub2)
|
||||
|
||||
// signing with the loaded key should match the original pubkey
|
||||
sig = priv2.Sign(msg)
|
||||
valid = pub.VerifyBytes(msg, sig)
|
||||
assert.True(valid)
|
||||
|
||||
// make sure pubkeys serialize properly as well
|
||||
bs = pub.Bytes()
|
||||
bpub, err := crypto.PubKeyFromBytes(bs)
|
||||
require.NoError(err)
|
||||
assert.Equal(pub, bpub)
|
||||
}
|
||||
|
||||
// TestRealLedgerErrorHandling calls. These tests assume
|
||||
// the ledger is not plugged in....
|
||||
func TestRealLedgerErrorHandling(t *testing.T) {
|
||||
require := rqr.New(t)
|
||||
|
||||
if os.Getenv("WITH_LEDGER") != "" {
|
||||
t.Skip("Skipping on WITH_LEDGER as it tests unplugged cases")
|
||||
}
|
||||
|
||||
// first, try to generate a key, must return an error
|
||||
// (no panic)
|
||||
_, err := NewPrivKeyLedgerEd25519()
|
||||
require.Error(err)
|
||||
|
||||
led := PrivKeyLedgerEd25519{} // empty
|
||||
// or with some pub key
|
||||
ed := crypto.GenPrivKeyEd25519()
|
||||
led2 := PrivKeyLedgerEd25519{CachedPubKey: ed.PubKey()}
|
||||
|
||||
// loading these should return errors
|
||||
bs := led.Bytes()
|
||||
_, err = crypto.PrivKeyFromBytes(bs)
|
||||
require.Error(err)
|
||||
|
||||
bs = led2.Bytes()
|
||||
_, err = crypto.PrivKeyFromBytes(bs)
|
||||
require.Error(err)
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package nano
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha512"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
App = 0x80
|
||||
Init = 0x00
|
||||
Update = 0x01
|
||||
Digest = 0x02
|
||||
MaxChunk = 253
|
||||
KeyLength = 32
|
||||
SigLength = 64
|
||||
)
|
||||
|
||||
var separator = []byte{0, 0xCA, 0xFE, 0}
|
||||
|
||||
func generateSignRequests(payload []byte) [][]byte {
|
||||
// nice one-shot
|
||||
digest := []byte{App, Digest}
|
||||
if len(payload) < MaxChunk {
|
||||
return [][]byte{append(digest, payload...)}
|
||||
}
|
||||
|
||||
// large payload is multi-chunk
|
||||
result := [][]byte{{App, Init}}
|
||||
update := []byte{App, Update}
|
||||
for len(payload) > MaxChunk {
|
||||
msg := append(update, payload[:MaxChunk]...)
|
||||
payload = payload[MaxChunk:]
|
||||
result = append(result, msg)
|
||||
}
|
||||
result = append(result, append(update, payload...))
|
||||
result = append(result, digest)
|
||||
return result
|
||||
}
|
||||
|
||||
func parseDigest(resp []byte) (key, sig []byte, err error) {
|
||||
if resp[0] != App || resp[1] != Digest {
|
||||
return nil, nil, errors.New("Invalid header")
|
||||
}
|
||||
resp = resp[2:]
|
||||
if len(resp) != KeyLength+SigLength+len(separator) {
|
||||
return nil, nil, errors.Errorf("Incorrect length: %d", len(resp))
|
||||
}
|
||||
|
||||
key, resp = resp[:KeyLength], resp[KeyLength:]
|
||||
if !bytes.Equal(separator, resp[:len(separator)]) {
|
||||
return nil, nil, errors.New("Cannot find 0xCAFE")
|
||||
}
|
||||
|
||||
sig = resp[len(separator):]
|
||||
return key, sig, nil
|
||||
}
|
||||
|
||||
func hashMsg(data []byte) []byte {
|
||||
res := sha512.Sum512(data)
|
||||
return res[:]
|
||||
}
|
|
@ -1,160 +0,0 @@
|
|||
package nano
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
asrt "github.com/stretchr/testify/assert"
|
||||
rqr "github.com/stretchr/testify/require"
|
||||
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
)
|
||||
|
||||
func parseEdKey(data []byte) (key crypto.PubKey, err error) {
|
||||
ed := crypto.PubKeyEd25519{}
|
||||
if len(data) < len(ed) {
|
||||
return key, errors.Errorf("Key length too short: %d", len(data))
|
||||
}
|
||||
copy(ed[:], data)
|
||||
return ed.Wrap(), nil
|
||||
}
|
||||
|
||||
func parseSig(data []byte) (key crypto.Signature, err error) {
|
||||
ed := crypto.SignatureEd25519{}
|
||||
if len(data) < len(ed) {
|
||||
return key, errors.Errorf("Sig length too short: %d", len(data))
|
||||
}
|
||||
copy(ed[:], data)
|
||||
return ed.Wrap(), nil
|
||||
}
|
||||
|
||||
func TestParseDigest(t *testing.T) {
|
||||
assert, require := asrt.New(t), rqr.New(t)
|
||||
|
||||
cases := []struct {
|
||||
output string
|
||||
key string
|
||||
sig string
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
output: "80028E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C9300CAFE00787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400",
|
||||
key: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93",
|
||||
sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
output: "800235467890876543525437890796574535467890",
|
||||
key: "",
|
||||
sig: "",
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
msg, err := hex.DecodeString(tc.output)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
|
||||
lKey, lSig, err := parseDigest(msg)
|
||||
if !tc.valid {
|
||||
assert.NotNil(err, "%d", i)
|
||||
} else if assert.Nil(err, "%d: %+v", i, err) {
|
||||
key, err := hex.DecodeString(tc.key)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
sig, err := hex.DecodeString(tc.sig)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
|
||||
assert.Equal(key, lKey, "%d", i)
|
||||
assert.Equal(sig, lSig, "%d", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type cryptoCase struct {
|
||||
msg string
|
||||
key string
|
||||
sig string
|
||||
valid bool
|
||||
}
|
||||
|
||||
func toBytes(c cryptoCase) (msg, key, sig []byte, err error) {
|
||||
msg, err = hex.DecodeString(c.msg)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
key, err = hex.DecodeString(c.key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sig, err = hex.DecodeString(c.sig)
|
||||
return
|
||||
}
|
||||
|
||||
func TestCryptoConvert(t *testing.T) {
|
||||
assert, require := asrt.New(t), rqr.New(t)
|
||||
|
||||
cases := []cryptoCase{
|
||||
0: {
|
||||
msg: "F00D",
|
||||
key: "8E8754F012C2FDB492183D41437FD837CB81D8BBE731924E2E0DAF43FD3F2C93",
|
||||
sig: "787DC03E9E4EE05983E30BAE0DEFB8DB0671DBC2F5874AC93F8D8CA4018F7A42D6F9A9BCEADB422AC8E27CEE9CA205A0B88D22CD686F0A43EB806E8190A3C400",
|
||||
valid: true,
|
||||
},
|
||||
1: {
|
||||
msg: "DEADBEEF",
|
||||
key: "0C45ADC887A5463F668533443C829ED13EA8E2E890C778957DC28DB9D2AD5A6C",
|
||||
sig: "00ED74EED8FDAC7988A14BF6BC222120CBAC249D569AF4C2ADABFC86B792F97DF73C4919BE4B6B0ACB53547273BF29FBF0A9E0992FFAB6CB6C9B09311FC86A00",
|
||||
valid: true,
|
||||
},
|
||||
2: {
|
||||
msg: "1234567890AA",
|
||||
key: "598FC1F0C76363D14D7480736DEEF390D85863360F075792A6975EFA149FD7EA",
|
||||
sig: "59AAB7D7BDC4F936B6415DE672A8B77FA6B8B3451CD95B3A631F31F9A05DAEEE5E7E4F89B64DDEBB5F63DC042CA13B8FCB8185F82AD7FD5636FFDA6B0DC9570B",
|
||||
valid: true,
|
||||
},
|
||||
3: {
|
||||
msg: "1234432112344321",
|
||||
key: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120",
|
||||
sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208",
|
||||
valid: true,
|
||||
},
|
||||
4: {
|
||||
msg: "12344321123443",
|
||||
key: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120",
|
||||
sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208",
|
||||
valid: false,
|
||||
},
|
||||
5: {
|
||||
msg: "1234432112344321",
|
||||
key: "459E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120",
|
||||
sig: "616B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208",
|
||||
valid: false,
|
||||
},
|
||||
6: {
|
||||
msg: "1234432112344321",
|
||||
key: "359E0636E780457294CCA5D2D84DB190C3EDBD6879729C10D3963DEA1D5D8120",
|
||||
sig: "716B44EC7A65E7C719C170D669A47DE80C6AC0BB13FBCC89230976F9CC14D4CF9ECF26D4AFBB9FFF625599F1FF6F78EDA15E9F6B6BDCE07CFE9D8C407AC45208",
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
msg, key, sig, err := toBytes(tc)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
|
||||
pk, err := parseEdKey(key)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
psig, err := parseSig(sig)
|
||||
require.Nil(err, "%d: %+v", i, err)
|
||||
|
||||
// it is not the signature of the message itself
|
||||
valid := pk.VerifyBytes(msg, psig)
|
||||
assert.False(valid, "%d", i)
|
||||
|
||||
// but rather of the hash of the msg
|
||||
hmsg := hashMsg(msg)
|
||||
valid = pk.VerifyBytes(hmsg, psig)
|
||||
assert.Equal(tc.valid, valid, "%d", i)
|
||||
}
|
||||
}
|
|
@ -110,6 +110,10 @@ func (kb dbKeybase) Sign(name, passphrase string, msg []byte) (sig crypto.Signat
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
if info.PrivKeyArmor == "" {
|
||||
err = fmt.Errorf("private key not available")
|
||||
return
|
||||
}
|
||||
priv, err := unarmorDecryptPrivKey(info.PrivKeyArmor, passphrase)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -127,6 +131,21 @@ func (kb dbKeybase) Export(name string) (armor string, err error) {
|
|||
return armorInfoBytes(bz), nil
|
||||
}
|
||||
|
||||
// ExportPubKey returns public keys in ASCII armored format.
|
||||
// Retrieve a Info object by its name and return the public key in
|
||||
// a portable format.
|
||||
func (kb dbKeybase) ExportPubKey(name string) (armor string, err error) {
|
||||
bz := kb.db.Get(infoKey(name))
|
||||
if bz == nil {
|
||||
return "", errors.New("No key to export with name " + name)
|
||||
}
|
||||
info, err := readInfo(bz)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return armorPubKeyBytes(info.PubKey.Bytes()), nil
|
||||
}
|
||||
|
||||
func (kb dbKeybase) Import(name string, armor string) (err error) {
|
||||
bz := kb.db.Get(infoKey(name))
|
||||
if len(bz) > 0 {
|
||||
|
@ -140,6 +159,26 @@ func (kb dbKeybase) Import(name string, armor string) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ImportPubKey imports ASCII-armored public keys.
|
||||
// Store a new Info object holding a public key only, i.e. it will
|
||||
// not be possible to sign with it as it lacks the secret key.
|
||||
func (kb dbKeybase) ImportPubKey(name string, armor string) (err error) {
|
||||
bz := kb.db.Get(infoKey(name))
|
||||
if len(bz) > 0 {
|
||||
return errors.New("Cannot overwrite data for name " + name)
|
||||
}
|
||||
pubBytes, err := unarmorPubKeyBytes(armor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
pubKey, err := crypto.PubKeyFromBytes(pubBytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
kb.writePubKey(pubKey, name)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes key forever, but we must present the
|
||||
// proper passphrase before deleting it (for security).
|
||||
func (kb dbKeybase) Delete(name, passphrase string) error {
|
||||
|
@ -174,6 +213,16 @@ func (kb dbKeybase) Update(name, oldpass, newpass string) error {
|
|||
kb.writeKey(key, name, newpass)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writePubKey(pub crypto.PubKey, name string) Info {
|
||||
// make Info
|
||||
info := newInfo(name, pub, "")
|
||||
|
||||
// write them both
|
||||
kb.db.SetSync(infoKey(name), info.bytes())
|
||||
return info
|
||||
}
|
||||
|
||||
func (kb dbKeybase) writeKey(priv crypto.PrivKey, name, passphrase string) Info {
|
||||
// generate the encrypted privkey
|
||||
privArmor := encryptArmorPrivKey(priv, passphrase)
|
||||
|
|
|
@ -91,8 +91,8 @@ func TestSignVerify(t *testing.T) {
|
|||
)
|
||||
algo := keys.AlgoSecp256k1
|
||||
|
||||
n1, n2 := "some dude", "a dudette"
|
||||
p1, p2 := "1234", "foobar"
|
||||
n1, n2, n3 := "some dude", "a dudette", "dude-ish"
|
||||
p1, p2, p3 := "1234", "foobar", "foobar"
|
||||
|
||||
// create two users and get their info
|
||||
i1, _, err := cstore.Create(n1, p1, algo)
|
||||
|
@ -101,9 +101,18 @@ func TestSignVerify(t *testing.T) {
|
|||
i2, _, err := cstore.Create(n2, p2, algo)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Import a public key
|
||||
armor, err := cstore.ExportPubKey(n2)
|
||||
require.Nil(t, err)
|
||||
cstore.ImportPubKey(n3, armor)
|
||||
i3, err := cstore.Get(n3)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, i3.PrivKeyArmor, "")
|
||||
|
||||
// let's try to sign some messages
|
||||
d1 := []byte("my first message")
|
||||
d2 := []byte("some other important info!")
|
||||
d3 := []byte("feels like I forgot something...")
|
||||
|
||||
// try signing both data with both keys...
|
||||
s11, pub1, err := cstore.Sign(n1, p1, d1)
|
||||
|
@ -145,6 +154,10 @@ func TestSignVerify(t *testing.T) {
|
|||
valid := tc.key.VerifyBytes(tc.data, tc.sig)
|
||||
assert.Equal(t, tc.valid, valid, "%d", i)
|
||||
}
|
||||
|
||||
// Now try to sign data with a secret-less key
|
||||
_, _, err = cstore.Sign(n3, p3, d3)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -243,6 +256,48 @@ func TestExportImport(t *testing.T) {
|
|||
assert.Equal(t, john, john2)
|
||||
}
|
||||
|
||||
func TestExportImportPubKey(t *testing.T) {
|
||||
// make the storage with reasonable defaults
|
||||
db := dbm.NewMemDB()
|
||||
cstore := keys.New(
|
||||
db,
|
||||
words.MustLoadCodec("english"),
|
||||
)
|
||||
|
||||
// Create a private-public key pair and ensure consistency
|
||||
info, _, err := cstore.Create("john", "passphrase", keys.AlgoEd25519)
|
||||
assert.Nil(t, err)
|
||||
assert.NotEqual(t, info.PrivKeyArmor, "")
|
||||
assert.Equal(t, info.Name, "john")
|
||||
addr := info.PubKey.Address()
|
||||
john, err := cstore.Get("john")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, john.Name, "john")
|
||||
assert.Equal(t, john.PubKey.Address(), addr)
|
||||
|
||||
// Export the public key only
|
||||
armor, err := cstore.ExportPubKey("john")
|
||||
assert.Nil(t, err)
|
||||
// Import it under a different name
|
||||
err = cstore.ImportPubKey("john-pubkey-only", armor)
|
||||
assert.Nil(t, err)
|
||||
// Ensure consistency
|
||||
john2, err := cstore.Get("john-pubkey-only")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, john2.PrivKeyArmor, "")
|
||||
// Compare the public keys
|
||||
assert.True(t, john.PubKey.Equals(john2.PubKey))
|
||||
// Ensure the original key hasn't changed
|
||||
john, err = cstore.Get("john")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, john.PubKey.Address(), addr)
|
||||
assert.Equal(t, john.Name, "john")
|
||||
|
||||
// Ensure keys cannot be overwritten
|
||||
err = cstore.ImportPubKey("john-pubkey-only", armor)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
// TestAdvancedKeyManagement verifies update, import, export functionality
|
||||
func TestAdvancedKeyManagement(t *testing.T) {
|
||||
|
||||
|
|
|
@ -13,24 +13,40 @@ import (
|
|||
const (
|
||||
blockTypePrivKey = "TENDERMINT PRIVATE KEY"
|
||||
blockTypeKeyInfo = "TENDERMINT KEY INFO"
|
||||
blockTypePubKey = "TENDERMINT PUBLIC KEY"
|
||||
)
|
||||
|
||||
func armorInfoBytes(bz []byte) string {
|
||||
return armorBytes(bz, blockTypeKeyInfo)
|
||||
}
|
||||
|
||||
func armorPubKeyBytes(bz []byte) string {
|
||||
return armorBytes(bz, blockTypePubKey)
|
||||
}
|
||||
|
||||
func armorBytes(bz []byte, blockType string) string {
|
||||
header := map[string]string{
|
||||
"type": "Info",
|
||||
"version": "0.0.0",
|
||||
}
|
||||
armorStr := crypto.EncodeArmor(blockTypeKeyInfo, header, bz)
|
||||
return armorStr
|
||||
return crypto.EncodeArmor(blockType, header, bz)
|
||||
}
|
||||
|
||||
func unarmorInfoBytes(armorStr string) (bz []byte, err error) {
|
||||
blockType, header, bz, err := crypto.DecodeArmor(armorStr)
|
||||
return unarmorBytes(armorStr, blockTypeKeyInfo)
|
||||
}
|
||||
|
||||
func unarmorPubKeyBytes(armorStr string) (bz []byte, err error) {
|
||||
return unarmorBytes(armorStr, blockTypePubKey)
|
||||
}
|
||||
|
||||
func unarmorBytes(armorStr, blockType string) (bz []byte, err error) {
|
||||
bType, header, bz, err := crypto.DecodeArmor(armorStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if blockType != blockTypeKeyInfo {
|
||||
err = fmt.Errorf("Unrecognized armor type: %v", blockType)
|
||||
if bType != blockType {
|
||||
err = fmt.Errorf("Unrecognized armor type %q, expected: %q", bType, blockType)
|
||||
return
|
||||
}
|
||||
if header["version"] != "0.0.0" {
|
||||
|
|
|
@ -18,7 +18,9 @@ type Keybase interface {
|
|||
Delete(name, passphrase string) error
|
||||
|
||||
Import(name string, armor string) (err error)
|
||||
ImportPubKey(name string, armor string) (err error)
|
||||
Export(name string) (armor string, err error)
|
||||
ExportPubKey(name string) (armor string, err error)
|
||||
}
|
||||
|
||||
// Info is the public information about a key
|
||||
|
|
|
@ -86,7 +86,7 @@ func keysWordsWordlistChinese_simplifiedTxt() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "keys/words/wordlist/chinese_simplified.txt", size: 8192, mode: os.FileMode(420), modTime: time.Unix(1514928181, 0)}
|
||||
info := bindataFileInfo{name: "keys/words/wordlist/chinese_simplified.txt", size: 8192, mode: os.FileMode(420), modTime: time.Unix(1523115814, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ func keysWordsWordlistEnglishTxt() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "keys/words/wordlist/english.txt", size: 13116, mode: os.FileMode(420), modTime: time.Unix(1514928181, 0)}
|
||||
info := bindataFileInfo{name: "keys/words/wordlist/english.txt", size: 13116, mode: os.FileMode(420), modTime: time.Unix(1523115814, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ func keysWordsWordlistJapaneseTxt() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "keys/words/wordlist/japanese.txt", size: 24287, mode: os.FileMode(420), modTime: time.Unix(1514928181, 0)}
|
||||
info := bindataFileInfo{name: "keys/words/wordlist/japanese.txt", size: 24287, mode: os.FileMode(420), modTime: time.Unix(1523115814, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ func keysWordsWordlistSpanishTxt() (*asset, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
info := bindataFileInfo{name: "keys/words/wordlist/spanish.txt", size: 13659, mode: os.FileMode(420), modTime: time.Unix(1514928181, 0)}
|
||||
info := bindataFileInfo{name: "keys/words/wordlist/spanish.txt", size: 13659, mode: os.FileMode(420), modTime: time.Unix(1523115814, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
## Simple Merkle Tree
|
||||
|
||||
For smaller static data structures that don't require immutable snapshots or mutability;
|
||||
for instance the transactions and validation signatures of a block can be hashed using this simple merkle tree logic.
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
Package merkle computes a deterministic minimal height Merkle tree hash.
|
||||
If the number of items is not a power of two, some leaves
|
||||
will be at different levels. Tries to keep both sides of
|
||||
the tree the same size, but the left may be one greater.
|
||||
|
||||
Use this for short deterministic trees, such as the validator list.
|
||||
For larger datasets, use IAVLTree.
|
||||
|
||||
Be aware that the current implementation by itself does not prevent
|
||||
second pre-image attacks. Hence, use this library with caution.
|
||||
Otherwise you might run into similar issues as, e.g., in early Bitcoin:
|
||||
https://bitcointalk.org/?topic=102395
|
||||
|
||||
*
|
||||
/ \
|
||||
/ \
|
||||
/ \
|
||||
/ \
|
||||
* *
|
||||
/ \ / \
|
||||
/ \ / \
|
||||
/ \ / \
|
||||
* * * h6
|
||||
/ \ / \ / \
|
||||
h0 h1 h2 h3 h4 h5
|
||||
|
||||
TODO(ismail): add 2nd pre-image protection or clarify further on how we use this and why this secure.
|
||||
|
||||
*/
|
||||
package merkle
|
|
@ -0,0 +1,91 @@
|
|||
package merkle
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-crypto/tmhash"
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
// Merkle tree from a map.
|
||||
// Leaves are `hash(key) | hash(value)`.
|
||||
// Leaves are sorted before Merkle hashing.
|
||||
type simpleMap struct {
|
||||
kvs cmn.KVPairs
|
||||
sorted bool
|
||||
}
|
||||
|
||||
func newSimpleMap() *simpleMap {
|
||||
return &simpleMap{
|
||||
kvs: nil,
|
||||
sorted: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Set hashes the key and value and appends it to the kv pairs.
|
||||
func (sm *simpleMap) Set(key string, value Hasher) {
|
||||
sm.sorted = false
|
||||
|
||||
// Hash the key to blind it... why not?
|
||||
khash := tmhash.Sum([]byte(key))
|
||||
|
||||
// And the value is hashed too, so you can
|
||||
// check for equality with a cached value (say)
|
||||
// and make a determination to fetch or not.
|
||||
vhash := value.Hash()
|
||||
|
||||
sm.kvs = append(sm.kvs, cmn.KVPair{
|
||||
Key: khash,
|
||||
Value: vhash,
|
||||
})
|
||||
}
|
||||
|
||||
// Hash Merkle root hash of items sorted by key
|
||||
// (UNSTABLE: and by value too if duplicate key).
|
||||
func (sm *simpleMap) Hash() []byte {
|
||||
sm.Sort()
|
||||
return hashKVPairs(sm.kvs)
|
||||
}
|
||||
|
||||
func (sm *simpleMap) Sort() {
|
||||
if sm.sorted {
|
||||
return
|
||||
}
|
||||
sm.kvs.Sort()
|
||||
sm.sorted = true
|
||||
}
|
||||
|
||||
// Returns a copy of sorted KVPairs.
|
||||
// NOTE these contain the hashed key and value.
|
||||
func (sm *simpleMap) KVPairs() cmn.KVPairs {
|
||||
sm.Sort()
|
||||
kvs := make(cmn.KVPairs, len(sm.kvs))
|
||||
copy(kvs, sm.kvs)
|
||||
return kvs
|
||||
}
|
||||
|
||||
//----------------------------------------
|
||||
|
||||
// A local extension to KVPair that can be hashed.
|
||||
// Key and value are length prefixed and concatenated,
|
||||
// then hashed.
|
||||
type kvPair cmn.KVPair
|
||||
|
||||
func (kv kvPair) Hash() []byte {
|
||||
hasher := tmhash.New()
|
||||
err := encodeByteSlice(hasher, kv.Key)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = encodeByteSlice(hasher, kv.Value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return hasher.Sum(nil)
|
||||
}
|
||||
|
||||
func hashKVPairs(kvs cmn.KVPairs) []byte {
|
||||
kvsH := make([]Hasher, len(kvs))
|
||||
for i, kvp := range kvs {
|
||||
kvsH[i] = kvPair(kvp)
|
||||
}
|
||||
return SimpleHashFromHashers(kvsH)
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package merkle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tendermint/go-crypto/tmhash"
|
||||
)
|
||||
|
||||
type strHasher string
|
||||
|
||||
func (str strHasher) Hash() []byte {
|
||||
return tmhash.Sum([]byte(str))
|
||||
}
|
||||
|
||||
func TestSimpleMap(t *testing.T) {
|
||||
{
|
||||
db := newSimpleMap()
|
||||
db.Set("key1", strHasher("value1"))
|
||||
assert.Equal(t, "3dafc06a52039d029be57c75c9d16356a4256ef4", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
||||
}
|
||||
{
|
||||
db := newSimpleMap()
|
||||
db.Set("key1", strHasher("value2"))
|
||||
assert.Equal(t, "03eb5cfdff646bc4e80fec844e72fd248a1c6b2c", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
||||
}
|
||||
{
|
||||
db := newSimpleMap()
|
||||
db.Set("key1", strHasher("value1"))
|
||||
db.Set("key2", strHasher("value2"))
|
||||
assert.Equal(t, "acc3971eab8513171cc90ce8b74f368c38f9657d", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
||||
}
|
||||
{
|
||||
db := newSimpleMap()
|
||||
db.Set("key2", strHasher("value2")) // NOTE: out of order
|
||||
db.Set("key1", strHasher("value1"))
|
||||
assert.Equal(t, "acc3971eab8513171cc90ce8b74f368c38f9657d", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
||||
}
|
||||
{
|
||||
db := newSimpleMap()
|
||||
db.Set("key1", strHasher("value1"))
|
||||
db.Set("key2", strHasher("value2"))
|
||||
db.Set("key3", strHasher("value3"))
|
||||
assert.Equal(t, "0cd117ad14e6cd22edcd9aa0d84d7063b54b862f", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
||||
}
|
||||
{
|
||||
db := newSimpleMap()
|
||||
db.Set("key2", strHasher("value2")) // NOTE: out of order
|
||||
db.Set("key1", strHasher("value1"))
|
||||
db.Set("key3", strHasher("value3"))
|
||||
assert.Equal(t, "0cd117ad14e6cd22edcd9aa0d84d7063b54b862f", fmt.Sprintf("%x", db.Hash()), "Hash didn't match")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
package merkle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// SimpleProof represents a simple merkle proof.
|
||||
type SimpleProof struct {
|
||||
Aunts [][]byte `json:"aunts"` // Hashes from leaf's sibling to a root's child.
|
||||
}
|
||||
|
||||
// SimpleProofsFromHashers computes inclusion proof for given items.
|
||||
// proofs[0] is the proof for items[0].
|
||||
func SimpleProofsFromHashers(items []Hasher) (rootHash []byte, proofs []*SimpleProof) {
|
||||
trails, rootSPN := trailsFromHashers(items)
|
||||
rootHash = rootSPN.Hash
|
||||
proofs = make([]*SimpleProof, len(items))
|
||||
for i, trail := range trails {
|
||||
proofs[i] = &SimpleProof{
|
||||
Aunts: trail.FlattenAunts(),
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SimpleProofsFromMap generates proofs from a map. The keys/values of the map will be used as the keys/values
|
||||
// in the underlying key-value pairs.
|
||||
// The keys are sorted before the proofs are computed.
|
||||
func SimpleProofsFromMap(m map[string]Hasher) (rootHash []byte, proofs []*SimpleProof) {
|
||||
sm := newSimpleMap()
|
||||
for k, v := range m {
|
||||
sm.Set(k, v)
|
||||
}
|
||||
sm.Sort()
|
||||
kvs := sm.kvs
|
||||
kvsH := make([]Hasher, 0, len(kvs))
|
||||
for _, kvp := range kvs {
|
||||
kvsH = append(kvsH, kvPair(kvp))
|
||||
}
|
||||
return SimpleProofsFromHashers(kvsH)
|
||||
}
|
||||
|
||||
// Verify that leafHash is a leaf hash of the simple-merkle-tree
|
||||
// which hashes to rootHash.
|
||||
func (sp *SimpleProof) Verify(index int, total int, leafHash []byte, rootHash []byte) bool {
|
||||
computedHash := computeHashFromAunts(index, total, leafHash, sp.Aunts)
|
||||
return computedHash != nil && bytes.Equal(computedHash, rootHash)
|
||||
}
|
||||
|
||||
// String implements the stringer interface for SimpleProof.
|
||||
// It is a wrapper around StringIndented.
|
||||
func (sp *SimpleProof) String() string {
|
||||
return sp.StringIndented("")
|
||||
}
|
||||
|
||||
// StringIndented generates a canonical string representation of a SimpleProof.
|
||||
func (sp *SimpleProof) StringIndented(indent string) string {
|
||||
return fmt.Sprintf(`SimpleProof{
|
||||
%s Aunts: %X
|
||||
%s}`,
|
||||
indent, sp.Aunts,
|
||||
indent)
|
||||
}
|
||||
|
||||
// Use the leafHash and innerHashes to get the root merkle hash.
|
||||
// If the length of the innerHashes slice isn't exactly correct, the result is nil.
|
||||
// Recursive impl.
|
||||
func computeHashFromAunts(index int, total int, leafHash []byte, innerHashes [][]byte) []byte {
|
||||
if index >= total || index < 0 || total <= 0 {
|
||||
return nil
|
||||
}
|
||||
switch total {
|
||||
case 0:
|
||||
panic("Cannot call computeHashFromAunts() with 0 total")
|
||||
case 1:
|
||||
if len(innerHashes) != 0 {
|
||||
return nil
|
||||
}
|
||||
return leafHash
|
||||
default:
|
||||
if len(innerHashes) == 0 {
|
||||
return nil
|
||||
}
|
||||
numLeft := (total + 1) / 2
|
||||
if index < numLeft {
|
||||
leftHash := computeHashFromAunts(index, numLeft, leafHash, innerHashes[:len(innerHashes)-1])
|
||||
if leftHash == nil {
|
||||
return nil
|
||||
}
|
||||
return SimpleHashFromTwoHashes(leftHash, innerHashes[len(innerHashes)-1])
|
||||
}
|
||||
rightHash := computeHashFromAunts(index-numLeft, total-numLeft, leafHash, innerHashes[:len(innerHashes)-1])
|
||||
if rightHash == nil {
|
||||
return nil
|
||||
}
|
||||
return SimpleHashFromTwoHashes(innerHashes[len(innerHashes)-1], rightHash)
|
||||
}
|
||||
}
|
||||
|
||||
// SimpleProofNode is a helper structure to construct merkle proof.
|
||||
// The node and the tree is thrown away afterwards.
|
||||
// Exactly one of node.Left and node.Right is nil, unless node is the root, in which case both are nil.
|
||||
// node.Parent.Hash = hash(node.Hash, node.Right.Hash) or
|
||||
// hash(node.Left.Hash, node.Hash), depending on whether node is a left/right child.
|
||||
type SimpleProofNode struct {
|
||||
Hash []byte
|
||||
Parent *SimpleProofNode
|
||||
Left *SimpleProofNode // Left sibling (only one of Left,Right is set)
|
||||
Right *SimpleProofNode // Right sibling (only one of Left,Right is set)
|
||||
}
|
||||
|
||||
// FlattenAunts will return the inner hashes for the item corresponding to the leaf,
|
||||
// starting from a leaf SimpleProofNode.
|
||||
func (spn *SimpleProofNode) FlattenAunts() [][]byte {
|
||||
// Nonrecursive impl.
|
||||
innerHashes := [][]byte{}
|
||||
for spn != nil {
|
||||
if spn.Left != nil {
|
||||
innerHashes = append(innerHashes, spn.Left.Hash)
|
||||
} else if spn.Right != nil {
|
||||
innerHashes = append(innerHashes, spn.Right.Hash)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
spn = spn.Parent
|
||||
}
|
||||
return innerHashes
|
||||
}
|
||||
|
||||
// trails[0].Hash is the leaf hash for items[0].
|
||||
// trails[i].Parent.Parent....Parent == root for all i.
|
||||
func trailsFromHashers(items []Hasher) (trails []*SimpleProofNode, root *SimpleProofNode) {
|
||||
// Recursive impl.
|
||||
switch len(items) {
|
||||
case 0:
|
||||
return nil, nil
|
||||
case 1:
|
||||
trail := &SimpleProofNode{items[0].Hash(), nil, nil, nil}
|
||||
return []*SimpleProofNode{trail}, trail
|
||||
default:
|
||||
lefts, leftRoot := trailsFromHashers(items[:(len(items)+1)/2])
|
||||
rights, rightRoot := trailsFromHashers(items[(len(items)+1)/2:])
|
||||
rootHash := SimpleHashFromTwoHashes(leftRoot.Hash, rightRoot.Hash)
|
||||
root := &SimpleProofNode{rootHash, nil, nil, nil}
|
||||
leftRoot.Parent = root
|
||||
leftRoot.Right = rightRoot
|
||||
rightRoot.Parent = root
|
||||
rightRoot.Left = leftRoot
|
||||
return append(lefts, rights...), root
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package merkle
|
||||
|
||||
import (
|
||||
"github.com/tendermint/go-crypto/tmhash"
|
||||
)
|
||||
|
||||
// SimpleHashFromTwoHashes is the basic operation of the Merkle tree: Hash(left | right).
|
||||
func SimpleHashFromTwoHashes(left, right []byte) []byte {
|
||||
var hasher = tmhash.New()
|
||||
err := encodeByteSlice(hasher, left)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = encodeByteSlice(hasher, right)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return hasher.Sum(nil)
|
||||
}
|
||||
|
||||
// SimpleHashFromHashers computes a Merkle tree from items that can be hashed.
|
||||
func SimpleHashFromHashers(items []Hasher) []byte {
|
||||
hashes := make([][]byte, len(items))
|
||||
for i, item := range items {
|
||||
hash := item.Hash()
|
||||
hashes[i] = hash
|
||||
}
|
||||
return simpleHashFromHashes(hashes)
|
||||
}
|
||||
|
||||
// SimpleHashFromMap computes a Merkle tree from sorted map.
|
||||
// Like calling SimpleHashFromHashers with
|
||||
// `item = []byte(Hash(key) | Hash(value))`,
|
||||
// sorted by `item`.
|
||||
func SimpleHashFromMap(m map[string]Hasher) []byte {
|
||||
sm := newSimpleMap()
|
||||
for k, v := range m {
|
||||
sm.Set(k, v)
|
||||
}
|
||||
return sm.Hash()
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
|
||||
// Expects hashes!
|
||||
func simpleHashFromHashes(hashes [][]byte) []byte {
|
||||
// Recursive impl.
|
||||
switch len(hashes) {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return hashes[0]
|
||||
default:
|
||||
left := simpleHashFromHashes(hashes[:(len(hashes)+1)/2])
|
||||
right := simpleHashFromHashes(hashes[(len(hashes)+1)/2:])
|
||||
return SimpleHashFromTwoHashes(left, right)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
package merkle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
. "github.com/tendermint/tmlibs/test"
|
||||
|
||||
"testing"
|
||||
"github.com/tendermint/go-crypto/tmhash"
|
||||
)
|
||||
|
||||
type testItem []byte
|
||||
|
||||
func (tI testItem) Hash() []byte {
|
||||
return []byte(tI)
|
||||
}
|
||||
|
||||
func TestSimpleProof(t *testing.T) {
|
||||
|
||||
total := 100
|
||||
|
||||
items := make([]Hasher, total)
|
||||
for i := 0; i < total; i++ {
|
||||
items[i] = testItem(cmn.RandBytes(tmhash.Size))
|
||||
}
|
||||
|
||||
rootHash := SimpleHashFromHashers(items)
|
||||
|
||||
rootHash2, proofs := SimpleProofsFromHashers(items)
|
||||
|
||||
if !bytes.Equal(rootHash, rootHash2) {
|
||||
t.Errorf("Unmatched root hashes: %X vs %X", rootHash, rootHash2)
|
||||
}
|
||||
|
||||
// For each item, check the trail.
|
||||
for i, item := range items {
|
||||
itemHash := item.Hash()
|
||||
proof := proofs[i]
|
||||
|
||||
// Verify success
|
||||
ok := proof.Verify(i, total, itemHash, rootHash)
|
||||
if !ok {
|
||||
t.Errorf("Verification failed for index %v.", i)
|
||||
}
|
||||
|
||||
// Wrong item index should make it fail
|
||||
{
|
||||
ok = proof.Verify((i+1)%total, total, itemHash, rootHash)
|
||||
if ok {
|
||||
t.Errorf("Expected verification to fail for wrong index %v.", i)
|
||||
}
|
||||
}
|
||||
|
||||
// Trail too long should make it fail
|
||||
origAunts := proof.Aunts
|
||||
proof.Aunts = append(proof.Aunts, cmn.RandBytes(32))
|
||||
{
|
||||
ok = proof.Verify(i, total, itemHash, rootHash)
|
||||
if ok {
|
||||
t.Errorf("Expected verification to fail for wrong trail length.")
|
||||
}
|
||||
}
|
||||
proof.Aunts = origAunts
|
||||
|
||||
// Trail too short should make it fail
|
||||
proof.Aunts = proof.Aunts[0 : len(proof.Aunts)-1]
|
||||
{
|
||||
ok = proof.Verify(i, total, itemHash, rootHash)
|
||||
if ok {
|
||||
t.Errorf("Expected verification to fail for wrong trail length.")
|
||||
}
|
||||
}
|
||||
proof.Aunts = origAunts
|
||||
|
||||
// Mutating the itemHash should make it fail.
|
||||
ok = proof.Verify(i, total, MutateByteSlice(itemHash), rootHash)
|
||||
if ok {
|
||||
t.Errorf("Expected verification to fail for mutated leaf hash")
|
||||
}
|
||||
|
||||
// Mutating the rootHash should make it fail.
|
||||
ok = proof.Verify(i, total, itemHash, MutateByteSlice(rootHash))
|
||||
if ok {
|
||||
t.Errorf("Expected verification to fail for mutated root hash")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package merkle
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
amino "github.com/tendermint/go-amino"
|
||||
)
|
||||
|
||||
// Tree is a Merkle tree interface.
|
||||
type Tree interface {
|
||||
Size() (size int)
|
||||
Height() (height int8)
|
||||
Has(key []byte) (has bool)
|
||||
Proof(key []byte) (value []byte, proof []byte, exists bool) // TODO make it return an index
|
||||
Get(key []byte) (index int, value []byte, exists bool)
|
||||
GetByIndex(index int) (key []byte, value []byte)
|
||||
Set(key []byte, value []byte) (updated bool)
|
||||
Remove(key []byte) (value []byte, removed bool)
|
||||
HashWithCount() (hash []byte, count int)
|
||||
Hash() (hash []byte)
|
||||
Save() (hash []byte)
|
||||
Load(hash []byte)
|
||||
Copy() Tree
|
||||
Iterate(func(key []byte, value []byte) (stop bool)) (stopped bool)
|
||||
IterateRange(start []byte, end []byte, ascending bool, fx func(key []byte, value []byte) (stop bool)) (stopped bool)
|
||||
}
|
||||
|
||||
// Hasher represents a hashable piece of data which can be hashed in the Tree.
|
||||
type Hasher interface {
|
||||
Hash() []byte
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
// Uvarint length prefixed byteslice
|
||||
func encodeByteSlice(w io.Writer, bz []byte) (err error) {
|
||||
return amino.EncodeByteSlice(w, bz)
|
||||
}
|
|
@ -83,9 +83,10 @@ func (privKey PrivKeyEd25519) Generate(index int) PrivKeyEd25519 {
|
|||
panic(err)
|
||||
}
|
||||
newBytes := Sha256(bz)
|
||||
var newKey [64]byte
|
||||
copy(newKey[:], newBytes)
|
||||
return PrivKeyEd25519(newKey)
|
||||
newKey := new([64]byte)
|
||||
copy(newKey[:32], newBytes)
|
||||
ed25519.MakePublicKey(newKey)
|
||||
return PrivKeyEd25519(*newKey)
|
||||
}
|
||||
|
||||
func GenPrivKeyEd25519() PrivKeyEd25519 {
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
package crypto
|
||||
|
||||
/*
|
||||
package crypto_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
crypto "github.com/tendermint/go-crypto"
|
||||
)
|
||||
|
||||
func TestGeneratePrivKey(t *testing.T) {
|
||||
testPriv := crypto.GenPrivKeyEd25519()
|
||||
testGenerate := testPriv.Generate(1)
|
||||
signBytes := []byte("something to sign")
|
||||
assert.True(t, testGenerate.PubKey().VerifyBytes(signBytes, testGenerate.Sign(signBytes)))
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
type BadKey struct {
|
||||
PrivKeyEd25519
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
crand "crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"sync"
|
||||
|
@ -72,8 +73,12 @@ type randInfo struct {
|
|||
func (ri *randInfo) MixEntropy(seedBytes []byte) {
|
||||
ri.mtx.Lock()
|
||||
defer ri.mtx.Unlock()
|
||||
// Make new ri.seedBytes
|
||||
hashBytes := Sha256(seedBytes)
|
||||
// Make new ri.seedBytes using passed seedBytes and current ri.seedBytes:
|
||||
// ri.seedBytes = sha256( seedBytes || ri.seedBytes )
|
||||
h := sha256.New()
|
||||
h.Write(seedBytes)
|
||||
h.Write(ri.seedBytes[:])
|
||||
hashBytes := h.Sum(nil)
|
||||
hashBytes32 := [32]byte{}
|
||||
copy(hashBytes32[:], hashBytes)
|
||||
ri.seedBytes = xorBytes32(ri.seedBytes, hashBytes32)
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"crypto/subtle"
|
||||
|
||||
. "github.com/tendermint/tmlibs/common"
|
||||
)
|
||||
|
||||
|
@ -41,7 +42,7 @@ func (sig SignatureEd25519) String() string { return fmt.Sprintf("/%X.../", Fing
|
|||
|
||||
func (sig SignatureEd25519) Equals(other Signature) bool {
|
||||
if otherEd, ok := other.(SignatureEd25519); ok {
|
||||
return bytes.Equal(sig[:], otherEd[:])
|
||||
return subtle.ConstantTimeCompare(sig[:], otherEd[:]) == 1
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
@ -74,7 +75,7 @@ func (sig SignatureSecp256k1) String() string { return fmt.Sprintf("/%X.../", Fi
|
|||
|
||||
func (sig SignatureSecp256k1) Equals(other Signature) bool {
|
||||
if otherSecp, ok := other.(SignatureSecp256k1); ok {
|
||||
return bytes.Equal(sig[:], otherSecp[:])
|
||||
return subtle.ConstantTimeCompare(sig[:], otherSecp[:]) == 1
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package tmhash
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"hash"
|
||||
)
|
||||
|
||||
const (
|
||||
Size = 20
|
||||
BlockSize = sha256.BlockSize
|
||||
)
|
||||
|
||||
type sha256trunc struct {
|
||||
sha256 hash.Hash
|
||||
}
|
||||
|
||||
func (h sha256trunc) Write(p []byte) (n int, err error) {
|
||||
return h.sha256.Write(p)
|
||||
}
|
||||
func (h sha256trunc) Sum(b []byte) []byte {
|
||||
shasum := h.sha256.Sum(b)
|
||||
return shasum[:Size]
|
||||
}
|
||||
|
||||
func (h sha256trunc) Reset() {
|
||||
h.sha256.Reset()
|
||||
}
|
||||
|
||||
func (h sha256trunc) Size() int {
|
||||
return Size
|
||||
}
|
||||
|
||||
func (h sha256trunc) BlockSize() int {
|
||||
return h.sha256.BlockSize()
|
||||
}
|
||||
|
||||
// New returns a new hash.Hash.
|
||||
func New() hash.Hash {
|
||||
return sha256trunc{
|
||||
sha256: sha256.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// Sum returns the first 20 bytes of SHA256 of the bz.
|
||||
func Sum(bz []byte) []byte {
|
||||
hash := sha256.Sum256(bz)
|
||||
return hash[:Size]
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package tmhash_test
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tendermint/go-crypto/tmhash"
|
||||
)
|
||||
|
||||
func TestHash(t *testing.T) {
|
||||
testVector := []byte("abc")
|
||||
hasher := tmhash.New()
|
||||
hasher.Write(testVector)
|
||||
bz := hasher.Sum(nil)
|
||||
|
||||
hasher = sha256.New()
|
||||
hasher.Write(testVector)
|
||||
bz2 := hasher.Sum(nil)
|
||||
bz2 = bz2[:20]
|
||||
|
||||
assert.Equal(t, bz, bz2)
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
package crypto
|
||||
|
||||
const Version = "0.6.2"
|
||||
const Version = "0.7.0"
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
package xchacha20poly1305
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
var sigma = [4]uint32{0x61707865, 0x3320646e, 0x79622d32, 0x6b206574}
|
||||
|
||||
type xchacha20poly1305 struct {
|
||||
key [KeySize]byte
|
||||
}
|
||||
|
||||
const (
|
||||
// KeySize is the size of the key used by this AEAD, in bytes.
|
||||
KeySize = 32
|
||||
// NonceSize is the size of the nonce used with this AEAD, in bytes.
|
||||
NonceSize = 24
|
||||
)
|
||||
|
||||
//New xChaChapoly1305 AEAD with 24 byte nonces
|
||||
func New(key []byte) (cipher.AEAD, error) {
|
||||
if len(key) != KeySize {
|
||||
return nil, errors.New("chacha20poly1305: bad key length")
|
||||
}
|
||||
ret := new(xchacha20poly1305)
|
||||
copy(ret.key[:], key)
|
||||
return ret, nil
|
||||
|
||||
}
|
||||
func (c *xchacha20poly1305) NonceSize() int {
|
||||
return NonceSize
|
||||
}
|
||||
|
||||
func (c *xchacha20poly1305) Overhead() int {
|
||||
return 16
|
||||
}
|
||||
|
||||
func (c *xchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte {
|
||||
if len(nonce) != NonceSize {
|
||||
panic("xchacha20poly1305: bad nonce length passed to Seal")
|
||||
}
|
||||
|
||||
if uint64(len(plaintext)) > (1<<38)-64 {
|
||||
panic("xchacha20poly1305: plaintext too large")
|
||||
}
|
||||
|
||||
var subKey [KeySize]byte
|
||||
var hNonce [16]byte
|
||||
var subNonce [chacha20poly1305.NonceSize]byte
|
||||
copy(hNonce[:], nonce[:16])
|
||||
|
||||
HChaCha20(&subKey, &hNonce, &c.key)
|
||||
|
||||
chacha20poly1305, _ := chacha20poly1305.New(subKey[:])
|
||||
|
||||
copy(subNonce[4:], nonce[16:])
|
||||
|
||||
return chacha20poly1305.Seal(dst, subNonce[:], plaintext, additionalData)
|
||||
}
|
||||
|
||||
func (c *xchacha20poly1305) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
|
||||
if len(nonce) != NonceSize {
|
||||
return nil, fmt.Errorf("xchacha20poly1305: bad nonce length passed to Open")
|
||||
}
|
||||
if uint64(len(ciphertext)) > (1<<38)-48 {
|
||||
return nil, fmt.Errorf("xchacha20poly1305: ciphertext too large")
|
||||
}
|
||||
var subKey [KeySize]byte
|
||||
var hNonce [16]byte
|
||||
var subNonce [chacha20poly1305.NonceSize]byte
|
||||
copy(hNonce[:], nonce[:16])
|
||||
|
||||
HChaCha20(&subKey, &hNonce, &c.key)
|
||||
|
||||
chacha20poly1305, _ := chacha20poly1305.New(subKey[:])
|
||||
|
||||
copy(subNonce[4:], nonce[16:])
|
||||
|
||||
return chacha20poly1305.Open(dst, subNonce[:], ciphertext, additionalData)
|
||||
}
|
||||
|
||||
// The MIT License (MIT)
|
||||
|
||||
// Copyright (c) 2016 Andreas Auernhammer
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
// HChaCha20 generates 32 pseudo-random bytes from a 128 bit nonce and a 256 bit secret key.
|
||||
// It can be used as a key-derivation-function (KDF).
|
||||
func HChaCha20(out *[32]byte, nonce *[16]byte, key *[32]byte) { hChaCha20Generic(out, nonce, key) }
|
||||
|
||||
func hChaCha20Generic(out *[32]byte, nonce *[16]byte, key *[32]byte) {
|
||||
v00 := sigma[0]
|
||||
v01 := sigma[1]
|
||||
v02 := sigma[2]
|
||||
v03 := sigma[3]
|
||||
v04 := binary.LittleEndian.Uint32(key[0:])
|
||||
v05 := binary.LittleEndian.Uint32(key[4:])
|
||||
v06 := binary.LittleEndian.Uint32(key[8:])
|
||||
v07 := binary.LittleEndian.Uint32(key[12:])
|
||||
v08 := binary.LittleEndian.Uint32(key[16:])
|
||||
v09 := binary.LittleEndian.Uint32(key[20:])
|
||||
v10 := binary.LittleEndian.Uint32(key[24:])
|
||||
v11 := binary.LittleEndian.Uint32(key[28:])
|
||||
v12 := binary.LittleEndian.Uint32(nonce[0:])
|
||||
v13 := binary.LittleEndian.Uint32(nonce[4:])
|
||||
v14 := binary.LittleEndian.Uint32(nonce[8:])
|
||||
v15 := binary.LittleEndian.Uint32(nonce[12:])
|
||||
|
||||
for i := 0; i < 20; i += 2 {
|
||||
v00 += v04
|
||||
v12 ^= v00
|
||||
v12 = (v12 << 16) | (v12 >> 16)
|
||||
v08 += v12
|
||||
v04 ^= v08
|
||||
v04 = (v04 << 12) | (v04 >> 20)
|
||||
v00 += v04
|
||||
v12 ^= v00
|
||||
v12 = (v12 << 8) | (v12 >> 24)
|
||||
v08 += v12
|
||||
v04 ^= v08
|
||||
v04 = (v04 << 7) | (v04 >> 25)
|
||||
v01 += v05
|
||||
v13 ^= v01
|
||||
v13 = (v13 << 16) | (v13 >> 16)
|
||||
v09 += v13
|
||||
v05 ^= v09
|
||||
v05 = (v05 << 12) | (v05 >> 20)
|
||||
v01 += v05
|
||||
v13 ^= v01
|
||||
v13 = (v13 << 8) | (v13 >> 24)
|
||||
v09 += v13
|
||||
v05 ^= v09
|
||||
v05 = (v05 << 7) | (v05 >> 25)
|
||||
v02 += v06
|
||||
v14 ^= v02
|
||||
v14 = (v14 << 16) | (v14 >> 16)
|
||||
v10 += v14
|
||||
v06 ^= v10
|
||||
v06 = (v06 << 12) | (v06 >> 20)
|
||||
v02 += v06
|
||||
v14 ^= v02
|
||||
v14 = (v14 << 8) | (v14 >> 24)
|
||||
v10 += v14
|
||||
v06 ^= v10
|
||||
v06 = (v06 << 7) | (v06 >> 25)
|
||||
v03 += v07
|
||||
v15 ^= v03
|
||||
v15 = (v15 << 16) | (v15 >> 16)
|
||||
v11 += v15
|
||||
v07 ^= v11
|
||||
v07 = (v07 << 12) | (v07 >> 20)
|
||||
v03 += v07
|
||||
v15 ^= v03
|
||||
v15 = (v15 << 8) | (v15 >> 24)
|
||||
v11 += v15
|
||||
v07 ^= v11
|
||||
v07 = (v07 << 7) | (v07 >> 25)
|
||||
v00 += v05
|
||||
v15 ^= v00
|
||||
v15 = (v15 << 16) | (v15 >> 16)
|
||||
v10 += v15
|
||||
v05 ^= v10
|
||||
v05 = (v05 << 12) | (v05 >> 20)
|
||||
v00 += v05
|
||||
v15 ^= v00
|
||||
v15 = (v15 << 8) | (v15 >> 24)
|
||||
v10 += v15
|
||||
v05 ^= v10
|
||||
v05 = (v05 << 7) | (v05 >> 25)
|
||||
v01 += v06
|
||||
v12 ^= v01
|
||||
v12 = (v12 << 16) | (v12 >> 16)
|
||||
v11 += v12
|
||||
v06 ^= v11
|
||||
v06 = (v06 << 12) | (v06 >> 20)
|
||||
v01 += v06
|
||||
v12 ^= v01
|
||||
v12 = (v12 << 8) | (v12 >> 24)
|
||||
v11 += v12
|
||||
v06 ^= v11
|
||||
v06 = (v06 << 7) | (v06 >> 25)
|
||||
v02 += v07
|
||||
v13 ^= v02
|
||||
v13 = (v13 << 16) | (v13 >> 16)
|
||||
v08 += v13
|
||||
v07 ^= v08
|
||||
v07 = (v07 << 12) | (v07 >> 20)
|
||||
v02 += v07
|
||||
v13 ^= v02
|
||||
v13 = (v13 << 8) | (v13 >> 24)
|
||||
v08 += v13
|
||||
v07 ^= v08
|
||||
v07 = (v07 << 7) | (v07 >> 25)
|
||||
v03 += v04
|
||||
v14 ^= v03
|
||||
v14 = (v14 << 16) | (v14 >> 16)
|
||||
v09 += v14
|
||||
v04 ^= v09
|
||||
v04 = (v04 << 12) | (v04 >> 20)
|
||||
v03 += v04
|
||||
v14 ^= v03
|
||||
v14 = (v14 << 8) | (v14 >> 24)
|
||||
v09 += v14
|
||||
v04 ^= v09
|
||||
v04 = (v04 << 7) | (v04 >> 25)
|
||||
}
|
||||
|
||||
binary.LittleEndian.PutUint32(out[0:], v00)
|
||||
binary.LittleEndian.PutUint32(out[4:], v01)
|
||||
binary.LittleEndian.PutUint32(out[8:], v02)
|
||||
binary.LittleEndian.PutUint32(out[12:], v03)
|
||||
binary.LittleEndian.PutUint32(out[16:], v12)
|
||||
binary.LittleEndian.PutUint32(out[20:], v13)
|
||||
binary.LittleEndian.PutUint32(out[24:], v14)
|
||||
binary.LittleEndian.PutUint32(out[28:], v15)
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
package xchacha20poly1305
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func toHex(bits []byte) string {
|
||||
return hex.EncodeToString(bits)
|
||||
}
|
||||
|
||||
func fromHex(bits string) []byte {
|
||||
b, err := hex.DecodeString(bits)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func TestHChaCha20(t *testing.T) {
|
||||
for i, v := range hChaCha20Vectors {
|
||||
var key [32]byte
|
||||
var nonce [16]byte
|
||||
copy(key[:], v.key)
|
||||
copy(nonce[:], v.nonce)
|
||||
|
||||
HChaCha20(&key, &nonce, &key)
|
||||
if !bytes.Equal(key[:], v.keystream) {
|
||||
t.Errorf("Test %d: keystream mismatch:\n \t got: %s\n \t want: %s", i, toHex(key[:]), toHex(v.keystream))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hChaCha20Vectors = []struct {
|
||||
key, nonce, keystream []byte
|
||||
}{
|
||||
{
|
||||
fromHex("0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
fromHex("000000000000000000000000000000000000000000000000"),
|
||||
fromHex("1140704c328d1d5d0e30086cdf209dbd6a43b8f41518a11cc387b669b2ee6586"),
|
||||
},
|
||||
{
|
||||
fromHex("8000000000000000000000000000000000000000000000000000000000000000"),
|
||||
fromHex("000000000000000000000000000000000000000000000000"),
|
||||
fromHex("7d266a7fd808cae4c02a0a70dcbfbcc250dae65ce3eae7fc210f54cc8f77df86"),
|
||||
},
|
||||
{
|
||||
fromHex("0000000000000000000000000000000000000000000000000000000000000001"),
|
||||
fromHex("000000000000000000000000000000000000000000000002"),
|
||||
fromHex("e0c77ff931bb9163a5460c02ac281c2b53d792b1c43fea817e9ad275ae546963"),
|
||||
},
|
||||
{
|
||||
fromHex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"),
|
||||
fromHex("000102030405060708090a0b0c0d0e0f1011121314151617"),
|
||||
fromHex("51e3ff45a895675c4b33b46c64f4a9ace110d34df6a2ceab486372bacbd3eff6"),
|
||||
},
|
||||
{
|
||||
fromHex("24f11cce8a1b3d61e441561a696c1c1b7e173d084fd4812425435a8896a013dc"),
|
||||
fromHex("d9660c5900ae19ddad28d6e06e45fe5e"),
|
||||
fromHex("5966b3eec3bff1189f831f06afe4d4e3be97fa9235ec8c20d08acfbbb4e851e3"),
|
||||
},
|
||||
}
|
||||
|
||||
func TestVectors(t *testing.T) {
|
||||
for i, v := range vectors {
|
||||
if len(v.plaintext) == 0 {
|
||||
v.plaintext = make([]byte, len(v.ciphertext))
|
||||
}
|
||||
|
||||
var nonce [24]byte
|
||||
copy(nonce[:], v.nonce)
|
||||
|
||||
aead, err := New(v.key)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
dst := aead.Seal(nil, nonce[:], v.plaintext, v.ad)
|
||||
if !bytes.Equal(dst, v.ciphertext) {
|
||||
t.Errorf("Test %d: ciphertext mismatch:\n \t got: %s\n \t want: %s", i, toHex(dst), toHex(v.ciphertext))
|
||||
}
|
||||
open, err := aead.Open(nil, nonce[:], dst, v.ad)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !bytes.Equal(open, v.plaintext) {
|
||||
t.Errorf("Test %d: plaintext mismatch:\n \t got: %s\n \t want: %s", i, string(open), string(v.plaintext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var vectors = []struct {
|
||||
key, nonce, ad, plaintext, ciphertext []byte
|
||||
}{
|
||||
{
|
||||
[]byte{0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f},
|
||||
[]byte{0x07, 0x00, 0x00, 0x00, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b},
|
||||
[]byte{0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7},
|
||||
[]byte("Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."),
|
||||
[]byte{0x45, 0x3c, 0x06, 0x93, 0xa7, 0x40, 0x7f, 0x04, 0xff, 0x4c, 0x56, 0xae, 0xdb, 0x17, 0xa3, 0xc0, 0xa1, 0xaf, 0xff, 0x01, 0x17, 0x49, 0x30, 0xfc, 0x22, 0x28, 0x7c, 0x33, 0xdb, 0xcf, 0x0a, 0xc8, 0xb8, 0x9a, 0xd9, 0x29, 0x53, 0x0a, 0x1b, 0xb3, 0xab, 0x5e, 0x69, 0xf2, 0x4c, 0x7f, 0x60, 0x70, 0xc8, 0xf8, 0x40, 0xc9, 0xab, 0xb4, 0xf6, 0x9f, 0xbf, 0xc8, 0xa7, 0xff, 0x51, 0x26, 0xfa, 0xee, 0xbb, 0xb5, 0x58, 0x05, 0xee, 0x9c, 0x1c, 0xf2, 0xce, 0x5a, 0x57, 0x26, 0x32, 0x87, 0xae, 0xc5, 0x78, 0x0f, 0x04, 0xec, 0x32, 0x4c, 0x35, 0x14, 0x12, 0x2c, 0xfc, 0x32, 0x31, 0xfc, 0x1a, 0x8b, 0x71, 0x8a, 0x62, 0x86, 0x37, 0x30, 0xa2, 0x70, 0x2b, 0xb7, 0x63, 0x66, 0x11, 0x6b, 0xed, 0x09, 0xe0, 0xfd, 0x5c, 0x6d, 0x84, 0xb6, 0xb0, 0xc1, 0xab, 0xaf, 0x24, 0x9d, 0x5d, 0xd0, 0xf7, 0xf5, 0xa7, 0xea},
|
||||
},
|
||||
}
|
Loading…
Reference in New Issue