diff --git a/CHANGELOG.md b/CHANGELOG.md index 91dcfab52..5db3a6109 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/Makefile b/Makefile index 9ae475983..74af29941 100644 --- a/Makefile +++ b/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 diff --git a/README.md b/README.md index 959290573..be087e045 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/_nano/keys.go b/_nano/keys.go deleted file mode 100644 index 8cf1c3721..000000000 --- a/_nano/keys.go +++ /dev/null @@ -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} -} diff --git a/_nano/keys_test.go b/_nano/keys_test.go deleted file mode 100644 index fda096e29..000000000 --- a/_nano/keys_test.go +++ /dev/null @@ -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) -} diff --git a/_nano/sign.go b/_nano/sign.go deleted file mode 100644 index c40801583..000000000 --- a/_nano/sign.go +++ /dev/null @@ -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[:] -} diff --git a/_nano/sign_test.go b/_nano/sign_test.go deleted file mode 100644 index 18e4e0d0b..000000000 --- a/_nano/sign_test.go +++ /dev/null @@ -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) - } -} diff --git a/keys/keybase.go b/keys/keybase.go index 88a12000c..dd9726478 100644 --- a/keys/keybase.go +++ b/keys/keybase.go @@ -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) diff --git a/keys/keybase_test.go b/keys/keybase_test.go index 84c81c81f..aaf3b92fb 100644 --- a/keys/keybase_test.go +++ b/keys/keybase_test.go @@ -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) { diff --git a/keys/mintkey.go b/keys/mintkey.go index 73263b83a..264a9747d 100644 --- a/keys/mintkey.go +++ b/keys/mintkey.go @@ -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" { diff --git a/keys/types.go b/keys/types.go index 40751ad77..e1df644ab 100644 --- a/keys/types.go +++ b/keys/types.go @@ -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 diff --git a/keys/words/wordlist/wordlist.go b/keys/words/wordlist/wordlist.go index 4efff82cb..9ffbb1539 100644 --- a/keys/words/wordlist/wordlist.go +++ b/keys/words/wordlist/wordlist.go @@ -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 } diff --git a/merkle/README.md b/merkle/README.md new file mode 100644 index 000000000..c44978368 --- /dev/null +++ b/merkle/README.md @@ -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. diff --git a/merkle/doc.go b/merkle/doc.go new file mode 100644 index 000000000..da65dd858 --- /dev/null +++ b/merkle/doc.go @@ -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 \ No newline at end of file diff --git a/merkle/simple_map.go b/merkle/simple_map.go new file mode 100644 index 000000000..cde5924f4 --- /dev/null +++ b/merkle/simple_map.go @@ -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) +} diff --git a/merkle/simple_map_test.go b/merkle/simple_map_test.go new file mode 100644 index 000000000..a89289a8f --- /dev/null +++ b/merkle/simple_map_test.go @@ -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") + } +} diff --git a/merkle/simple_proof.go b/merkle/simple_proof.go new file mode 100644 index 000000000..f52d1ad9f --- /dev/null +++ b/merkle/simple_proof.go @@ -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 + } +} diff --git a/merkle/simple_tree.go b/merkle/simple_tree.go new file mode 100644 index 000000000..c23f84264 --- /dev/null +++ b/merkle/simple_tree.go @@ -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) + } +} diff --git a/merkle/simple_tree_test.go b/merkle/simple_tree_test.go new file mode 100644 index 000000000..db8e3d7ff --- /dev/null +++ b/merkle/simple_tree_test.go @@ -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") + } + } +} diff --git a/merkle/types.go b/merkle/types.go new file mode 100644 index 000000000..2fcb3f39d --- /dev/null +++ b/merkle/types.go @@ -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) +} diff --git a/priv_key.go b/priv_key.go index 61d373f60..82e1cfced 100644 --- a/priv_key.go +++ b/priv_key.go @@ -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 { diff --git a/priv_key_test.go b/priv_key_test.go index 3fbfba34d..6abe36ac5 100644 --- a/priv_key_test.go +++ b/priv_key_test.go @@ -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 } diff --git a/random.go b/random.go index 46754219d..66da035a9 100644 --- a/random.go +++ b/random.go @@ -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) diff --git a/signature.go b/signature.go index cfe927137..4f55420c5 100644 --- a/signature.go +++ b/signature.go @@ -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 } diff --git a/tmhash/hash.go b/tmhash/hash.go new file mode 100644 index 000000000..1b29d8680 --- /dev/null +++ b/tmhash/hash.go @@ -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] +} diff --git a/tmhash/hash_test.go b/tmhash/hash_test.go new file mode 100644 index 000000000..abf0247ab --- /dev/null +++ b/tmhash/hash_test.go @@ -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) +} diff --git a/version.go b/version.go index aac87c4f3..4cf5685a5 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package crypto -const Version = "0.6.2" +const Version = "0.7.0" diff --git a/xchacha20poly1305/xchachapoly.go b/xchacha20poly1305/xchachapoly.go new file mode 100644 index 000000000..9e0778579 --- /dev/null +++ b/xchacha20poly1305/xchachapoly.go @@ -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) +} diff --git a/xchacha20poly1305/xchachapoly_test.go b/xchacha20poly1305/xchachapoly_test.go new file mode 100644 index 000000000..3001217f4 --- /dev/null +++ b/xchacha20poly1305/xchachapoly_test.go @@ -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}, + }, +}