Skip to content

Commit

Permalink
Implement v0.2 of the Celo address based encryption protocol (ethereu…
Browse files Browse the repository at this point in the history
  • Loading branch information
asaj authored Aug 30, 2018
1 parent b826ab3 commit e628df6
Show file tree
Hide file tree
Showing 19 changed files with 228 additions and 100 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ jobs:
- checkout
- run: make lint
# TODO(celo): Use testing.Skip instead
- run: build/env.sh go run build/ci.go test --skip "github.com/ethereum/go-ethereum/cmd/swarm,github.com/ethereum/go-ethereum/swarm/network/simulations/discovery,github.com/ethereum/go-ethereum/swarm/network/stream"
- run: build/env.sh go run build/ci.go test --skip "github.com/ethereum/go-ethereum/cmd/swarm,github.com/ethereum/go-ethereum/swarm/network/simulations/discovery,github.com/ethereum/go-ethereum/swarm/pss/notify,github.com/ethereum/go-ethereum/swarm/network/stream"
100 changes: 61 additions & 39 deletions abe/abe.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,71 +19,93 @@ package abe
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"strconv"
"regexp"
"time"

"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
)

func SendVerificationTexts(receipts []*types.Receipt, block *types.Block, coinbase common.Address, accountManager *accounts.Manager) {
numTransactions := 0
for range block.Transactions() {
numTransactions += 1
func decryptPhoneNumber(request types.VerificationRequest, account accounts.Account, wallet accounts.Wallet) (string, error) {
phoneNumber, err := wallet.Decrypt(account, request.EncryptedPhone, nil, nil)
if err != nil {
return "", err
}
// TODO(asa): Better validation of phone numbers
r, _ := regexp.Compile(`^\+[0-9]{8,15}$`)
if !bytes.Equal(crypto.Keccak256(phoneNumber), request.PhoneHash.Bytes()) {
return string(phoneNumber), errors.New("Phone hash doesn't match decrypted phone number")
} else if !r.MatchString(string(phoneNumber)) {
return string(phoneNumber), fmt.Errorf("Decrypted phone number invalid: %s", string(phoneNumber))
}
log.Debug("\n[Celo] New block: "+block.Hash().Hex()+", "+strconv.Itoa(numTransactions), nil, nil)
return string(phoneNumber), nil
}

wallet, err := accountManager.Find(accounts.Account{Address: coinbase})
func createVerificationMessage(request types.VerificationRequest, account accounts.Account, wallet accounts.Wallet) (string, error) {
signature, err := wallet.SignHash(account, request.UnsignedMessageHash.Bytes())
if err != nil {
log.Error("[Celo] Failed to get account", "err", err)
return "", err
}
return fmt.Sprintf("Celo verification code: %s:%d", hexutil.Encode(signature), request.VerificationIndex), nil
}

func sendSms(phoneNumber string, message string) error {
// Send the actual text message using our mining pool.
// TODO: Make mining pool be configurable via command line arguments.
url := "https://mining-pool.celo.org/v0.1/sms"
values := map[string]string{"phoneNumber": phoneNumber, "message": message}
jsonValue, _ := json.Marshal(values)
var err error

// Retry 5 times if we fail.
for i := 0; i < 5; i++ {
_, err := http.Post(url, "application/json", bytes.NewBuffer(jsonValue))
if err == nil {
break
}
time.Sleep(100 * time.Millisecond)
}
return err
}

func SendVerificationMessages(receipts []*types.Receipt, block *types.Block, coinbase common.Address, accountManager *accounts.Manager) {
account := accounts.Account{Address: coinbase}
wallet, err := accountManager.Find(account)
if err != nil {
log.Error("[Celo] Failed to get account for sms verification", "err", err)
return
}

for _, receipt := range receipts {
for _, phone := range receipt.SmsQueue {
log.Debug("[Celo] Sending text to phone: "+phone, nil, nil)

if len(phone) <= 0 {
log.Error("[Celo] Invalid phone number: "+phone, nil, nil)
for _, request := range receipt.VerificationRequests {
if !bytes.Equal(coinbase.Bytes(), request.Verifier.Bytes()) {
continue
}

// Construct the secret code to be sent via SMS.
unsignedCode := common.BytesToHash([]byte(phone + block.Number().String()))
code, err := wallet.SignHash(accounts.Account{Address: coinbase}, unsignedCode.Bytes())
phoneNumber, err := decryptPhoneNumber(request, account, wallet)
if err != nil {
log.Error("[Celo] Failed to sign message for sending over SMS", "err", err)
log.Error("[Celo] Failed to decrypt phone number", "err", err)
continue
}
hexCode := hexutil.Encode(code[:])
log.Debug("[Celo] Secret code: "+hexCode+" "+string(len(code)), nil, nil)
secret := fmt.Sprintf("Gem verification code: %s", hexCode)
log.Debug("[Celo] New verification request: "+receipt.TxHash.Hex()+" "+phone, nil, nil)

// Send the actual text message using our mining pool.
// TODO: Make mining pool be configurable via command line arguments.
url := "https://mining-pool.celo.org/v0.1/sms"
values := map[string]string{"phoneNumber": phone, "message": secret}
jsonValue, _ := json.Marshal(values)
_, err = http.Post(url, "application/json", bytes.NewBuffer(jsonValue))
log.Debug("[Celo] SMS send Url: "+url, nil, nil)
log.Debug(fmt.Sprintf("[Celo] Phone number %s requesting verification", phoneNumber))

// Retry 5 times if we fail.
for i := 0; i < 5; i++ {
if err == nil {
break
}
log.Debug("[Celo] Got an error when trying to send SMS to: "+url, nil, nil)
time.Sleep(100 * time.Millisecond)
_, err = http.Post(url, "application/json", bytes.NewBuffer(jsonValue))
message, err := createVerificationMessage(request, account, wallet)
if err != nil {
log.Error("[Celo] Failed to create verification message", "err", err)
continue
}

log.Debug("[Celo] Sent SMS", nil, nil)
log.Debug(fmt.Sprintf("[Celo] Sending verification message: \"%s\"", message), nil, nil)
err = sendSms(phoneNumber, message)
if err != nil {
log.Error("[Celo] Failed to send SMS", "err", err)
}
}
}
}
3 changes: 3 additions & 0 deletions accounts/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ type Wallet interface {
// Contains returns whether an account is part of this particular wallet or not.
Contains(account Account) bool

// Decrypt decrypts an ECIES ciphertext.
Decrypt(account Account, c, s1, s2 []byte) ([]byte, error)

// Derive attempts to explicitly derive a hierarchical deterministic account at
// the specified derivation path. If requested, the derived account will be added
// to the wallet's tracked account list.
Expand Down
16 changes: 16 additions & 0 deletions accounts/keystore/keystore.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/ecies"
"github.com/ethereum/go-ethereum/event"
)

Expand Down Expand Up @@ -228,6 +229,21 @@ func (ks *KeyStore) Accounts() []accounts.Account {
return ks.cache.accounts()
}

// Decrypt decrypts an ECIES ciphertext.
func (ks *KeyStore) Decrypt(a accounts.Account, c, s1, s2 []byte) ([]byte, error) {
// Look up the key to sign with and abort if it cannot be found
ks.mu.RLock()
defer ks.mu.RUnlock()

unlockedKey, found := ks.unlocked[a.Address]
if !found {
return nil, ErrLocked
}
// Import the ECDSA key as an ECIES key and decrypt the data.
eciesKey := ecies.ImportECDSA(unlockedKey.PrivateKey)
return eciesKey.Decrypt(c, s1, s2)
}

// Delete deletes the key matched by account if the passphrase is correct.
// If the account contains no filename, the address must match a unique key.
func (ks *KeyStore) Delete(a accounts.Account, passphrase string) error {
Expand Down
12 changes: 12 additions & 0 deletions accounts/keystore/keystore_wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@ func (w *keystoreWallet) Contains(account accounts.Account) bool {
return account.Address == w.account.Address && (account.URL == (accounts.URL{}) || account.URL == w.account.URL)
}

// Decrypt decrypts an ECIES ciphertext.
func (w *keystoreWallet) Decrypt(account accounts.Account, c, s1, s2 []byte) ([]byte, error) {
if account.Address != w.account.Address {
return nil, accounts.ErrUnknownAccount
}
if account.URL != (accounts.URL{}) && account.URL != w.account.URL {
return nil, accounts.ErrUnknownAccount
}
// Account seems valid, request the keystore to sign
return w.keystore.Decrypt(account, c, s1, s2)
}

// Derive implements accounts.Wallet, but is a noop for plain wallets since there
// is no notion of hierarchical account derivation for plain keystore accounts.
func (w *keystoreWallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
Expand Down
4 changes: 4 additions & 0 deletions accounts/usbwallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,10 @@ func (w *wallet) Contains(account accounts.Account) bool {
return exists
}

func (w *wallet) Decrypt(a accounts.Account, c, s1, s2 []byte) ([]byte, error) {
return nil, accounts.ErrNotSupported
}

// Derive implements accounts.Wallet, deriving a new account at the specific
// derivation path. If pin is set to true, the account will be added to the list
// of tracked accounts.
Expand Down
7 changes: 5 additions & 2 deletions cmd/geth/dao_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ var daoOldGenesis = `{
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"signature" : "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00",
"config" : {}
}`
Expand All @@ -53,6 +54,7 @@ var daoNoForkGenesis = `{
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"signature" : "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00",
"config" : {
"daoForkBlock" : 314,
Expand All @@ -70,14 +72,15 @@ var daoProForkGenesis = `{
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"signature" : "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00",
"config" : {
"daoForkBlock" : 314,
"daoForkSupport" : true
}
}`

var daoGenesisHash = common.HexToHash("5e1fc79cb4ffa4739177b5408045cd5d51c6cf766133f23f7cd72ee1f8d790e0")
var daoGenesisHash = common.HexToHash("6e535a8af35e1da477617a04db449f1167824a15b1f0bbced3591b37725f1b6d")
var daoGenesisForkBlock = big.NewInt(314)

// TestDAOForkBlockNewChain tests that the DAO hard-fork number and the nodes support/opposition is correctly
Expand Down Expand Up @@ -127,7 +130,7 @@ func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBloc
}
defer db.Close()

genesisHash := common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
genesisHash := common.HexToHash("0x755c328ecddd758876f9466c8f0b6b607688b658ff8f625f1acc3fcb31788429")
if genesis != "" {
genesisHash = daoGenesisHash
}
Expand Down
10 changes: 7 additions & 3 deletions core/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,21 @@ import (
func TestDefaultGenesisBlock(t *testing.T) {
block := DefaultGenesisBlock().ToBlock(nil)
if block.Hash() != params.MainnetGenesisHash {
t.Errorf("wrong mainnet genesis hash, got %v, want %v", block.Hash(), params.MainnetGenesisHash)
t.Errorf("wrong mainnet genesis hash, got %v, want %v", block.Hash().Hex(), params.MainnetGenesisHash.Hex())
}
block = DefaultTestnetGenesisBlock().ToBlock(nil)
if block.Hash() != params.TestnetGenesisHash {
t.Errorf("wrong testnet genesis hash, got %v, want %v", block.Hash(), params.TestnetGenesisHash)
t.Errorf("wrong testnet genesis hash, got %v, want %v", block.Hash().Hex(), params.TestnetGenesisHash.Hex())
}
block = DefaultRinkebyGenesisBlock().ToBlock(nil)
if block.Hash() != params.RinkebyGenesisHash {
t.Errorf("wrong testnet genesis hash, got %v, want %v", block.Hash().Hex(), params.RinkebyGenesisHash.Hex())
}
}

func TestSetupGenesis(t *testing.T) {
var (
customghash = common.HexToHash("0x89c99d90b79719238d2645c7642f2c9295246e80775b38cfd162b696817fbd50")
customghash = common.HexToHash("0x12c995f892a22e8a2dcdfa53f8155c07679d104a979c8b047c8486232c966866")
customg = Genesis{
Config: &params.ChainConfig{HomesteadBlock: big.NewInt(3)},
Alloc: GenesisAlloc{
Expand Down
2 changes: 1 addition & 1 deletion core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
if msg.To() == nil {
receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())
}
receipt.SmsQueue = vmenv.SmsQueue
receipt.VerificationRequests = vmenv.VerificationRequests
// Set the receipt logs and create a bloom for filtering
receipt.Logs = statedb.GetLogs(tx.Hash())
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
Expand Down
7 changes: 7 additions & 0 deletions core/types/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ var (
// out on a block.
type BlockNonce [8]byte

// A BlockSignature is a 65-byte signature of the previous blockHash. This makes
// the public key available and verifies that the miner has access to the
// private key associated with the etherbase.
type BlockSignature [65]byte

// EncodeNonce converts the given integer to a block nonce.
func EncodeNonce(i uint64) BlockNonce {
var n BlockNonce
Expand Down Expand Up @@ -83,6 +88,7 @@ type Header struct {
Extra []byte `json:"extraData" gencodec:"required"`
MixDigest common.Hash `json:"mixHash" gencodec:"required"`
Nonce BlockNonce `json:"nonce" gencodec:"required"`
Signature BlockSignature `json:"signature" gencodec:"required"`
}

// field type overrides for gencodec
Expand Down Expand Up @@ -318,6 +324,7 @@ func (b *Block) TxHash() common.Hash { return b.header.TxHash }
func (b *Block) ReceiptHash() common.Hash { return b.header.ReceiptHash }
func (b *Block) UncleHash() common.Hash { return b.header.UncleHash }
func (b *Block) Extra() []byte { return common.CopyBytes(b.header.Extra) }
func (b *Block) Signature() []byte { return common.CopyBytes(b.header.Signature[:]) }

func (b *Block) Header() *Header { return CopyHeader(b.header) }

Expand Down
29 changes: 12 additions & 17 deletions core/types/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package types

import (
"bytes"
"fmt"
"math/big"
"reflect"
"testing"
Expand All @@ -29,7 +28,7 @@ import (

// from bcValidBlockTest.json, "SimpleTx"
func TestBlockEncoding(t *testing.T) {
blockEnc := common.FromHex("f90260f901f9a083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4f861f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1c0")
blockEnc := common.FromHex("0xf902bff90256a0df056b5ded5393055f6b3df0e5b5bb0a9b5d72b47066bd3089e66786b397be51a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479499d0747412109de2cf530e71a427e6f22ab881b2a02ec83283f8557ec0c85bc840d568ae9fd711ff7f1ec0eaec5e8cbd4a1435331aa0f3e88103399eb9e775501fa92e0af1493593d4ba9813e22c0dbf4a16bf60c8a0a0056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020180078347e7c4825208845b819d479ad98301080e846765746888676f312e31302e328664617277696ea0d198efa28bfa0825a13950dbbc1896e1d4e97aa9a834a198f8b0458e3e5e6a328800203cd21b41a98db8413e835d97c294e9c5a24e072eb6cb07c91f562042c4dac5e67229b5cea30b35c73258e1a426173c4267c480a13d7c8c0c92ca70d55837b706194b763e0d57186a01f863f861800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a808208bda0d34f8c5ff9814a2f173f17026ffe1811d6f4c17b192bde71fec0114a6619db69a0377e512f53d89a5f932f6cef6ad4076167f4000bc5ea6c8eaaffb873b61a67f9c0")
var block Block
if err := rlp.DecodeBytes(blockEnc, &block); err != nil {
t.Fatal("decode error: ", err)
Expand All @@ -40,25 +39,21 @@ func TestBlockEncoding(t *testing.T) {
t.Errorf("%s mismatch: got %v, want %v", f, got, want)
}
}
check("Difficulty", block.Difficulty(), big.NewInt(131072))
check("GasLimit", block.GasLimit(), uint64(3141592))
check("Difficulty", block.Difficulty(), big.NewInt(131456))
check("GasLimit", block.GasLimit(), uint64(4712388))
check("GasUsed", block.GasUsed(), uint64(21000))
check("Coinbase", block.Coinbase(), common.HexToAddress("8888f1f195afa192cfee860698584c030f4c9db1"))
check("MixDigest", block.MixDigest(), common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498"))
check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017"))
check("Hash", block.Hash(), common.HexToHash("0a5843ac1cb04865017cb35a57b50b07084e5fcee39b5acadade33149f4fff9e"))
check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4))
check("Time", block.Time(), big.NewInt(1426516743))
check("Coinbase", block.Coinbase(), common.HexToAddress("99d0747412109de2cf530e71a427e6f22ab881b2"))
check("MixDigest", block.MixDigest(), common.HexToHash("d198efa28bfa0825a13950dbbc1896e1d4e97aa9a834a198f8b0458e3e5e6a32"))
check("Root", block.Root(), common.HexToHash("2ec83283f8557ec0c85bc840d568ae9fd711ff7f1ec0eaec5e8cbd4a1435331a"))
check("Hash", block.Hash(), common.HexToHash("31dff76060aeef544c9a8b6ed42cef6260699a9aec35abcb9ffe09bb9c805468"))
check("Nonce", block.Nonce(), uint64(0x203CD21B41A98D))
check("Time", block.Time(), big.NewInt(1535221063))
check("Size", block.Size(), common.StorageSize(len(blockEnc)))
check("ParentHash", block.ParentHash(), common.HexToHash("df056b5ded5393055f6b3df0e5b5bb0a9b5d72b47066bd3089e66786b397be51"))
check("Signature", block.Signature(), common.FromHex("3e835d97c294e9c5a24e072eb6cb07c91f562042c4dac5e67229b5cea30b35c73258e1a426173c4267c480a13d7c8c0c92ca70d55837b706194b763e0d57186a01"))

tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), 50000, big.NewInt(10), nil)

tx1, _ = tx1.WithSignature(HomesteadSigner{}, common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100"))
fmt.Println(block.Transactions()[0].Hash())
fmt.Println(tx1.data)
fmt.Println(tx1.Hash())
check("len(Transactions)", len(block.Transactions()), 1)
check("Transactions[0].Hash", block.Transactions()[0].Hash(), tx1.Hash())
check("Transactions[0].Hash", block.Transactions()[0].Hash(), common.HexToHash("88d59983cc5bb16689ac6ae163b21311e85533d1698def009aa3becd9bff9eeb"))

ourBlockEnc, err := rlp.EncodeToBytes(&block)
if err != nil {
Expand Down
Loading

0 comments on commit e628df6

Please sign in to comment.