Initial commit

This commit is contained in:
BlockChainDev 2019-02-25 22:44:14 +00:00
commit cda7a31e4e
147 changed files with 10366 additions and 0 deletions

1
Dockerfile Normal file
View file

@ -0,0 +1 @@
FROM vidsyhq/go-base:latest

69
Gopkg.toml Normal file
View file

@ -0,0 +1,69 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
branch = "master"
name = "github.com/syndtr/goleveldb"
[[constraint]]
name = "github.com/urfave/cli"
version = "1.20.0"
[prune]
go-tests = true
unused-packages = true
[[constraint]]
branch = "master"
name = "golang.org/x/crypto"
[[constraint]]
branch = "master"
name = "github.com/anthdm/rfc6979"
[[constraint]]
name = "golang.org/x/text"
version = "0.3.0"
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.2.1"
[[constraint]]
name = "github.com/sirupsen/logrus"
version = "1.0.5"
[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.0"
[[constraint]]
name = "github.com/go-yaml/yaml"
version = "2.1.1"
[[constraint]]
name = "github.com/go-redis/redis"
version = "6.10.2"

57
Makefile Normal file
View file

@ -0,0 +1,57 @@
BRANCH = "master"
BUILD_TIME = "$(shell date -u +\"%Y-%m-%dT%H:%M:%SZ\")"
VERSION = $(shell cat ./VERSION)
REPONAME = "neo-go"
NETMODE ?= "privnet"
build:
@echo "=> Building darwin binary"
@go build -i -ldflags "-X github.com/CityOfZion/neo-go/config.Version=${VERSION}-dev -X github.com/CityOfZion/neo-go/config.BuildTime=${BUILD_TIME}" -o ./bin/neo-go ./cli/main.go
build-image:
docker build -t cityofzion/neo-go --build-arg VERSION=${VERSION} .
build-linux:
@echo "=> Building linux binary"
@GOOS=linux go build -i -ldflags "-X github.com/CityOfZion/neo-go/config.Version=${VERSION}-dev -X github.com/CityOfZion/neo-go/config.BuildTime=${BUILD_TIME}" -o ./bin/neo-go ./cli/main.go
check-version:
git fetch && (! git rev-list ${VERSION})
clean-cluster:
@echo "=> Removing all containers and chain storage"
@rm -rf chains/privnet-docker-one chains/privnet-docker-two chains/privnet-docker-three chains/privnet-docker-four
@docker-compose stop
@docker-compose rm -f
deps:
@dep ensure
push-tag:
git checkout ${BRANCH}
git pull origin ${BRANCH}
git tag ${VERSION}
git push origin ${VERSION}
push-to-registry:
@docker login -e ${DOCKER_EMAIL} -u ${DOCKER_USER} -p ${DOCKER_PASS}
@docker tag CityOfZion/${REPONAME}:latest CityOfZion/${REPONAME}:${CIRCLE_TAG}
@docker push CityOfZion/${REPONAME}:${CIRCLE_TAG}
@docker push CityOfZion/${REPONAME}
run: build
./bin/neo-go node -config-path ./config -${NETMODE}
run-cluster: build-linux
@echo "=> Starting docker-compose cluster"
@echo "=> Building container image"
@docker-compose build
@docker-compose up -d
@echo "=> Tailing logs, exiting this prompt will not stop the cluster"
@docker-compose logs -f
test:
@go test ./... -cover
vet:
@go vet ./...

1
VERSION Normal file
View file

@ -0,0 +1 @@
0.44.8

152
circle.yml Normal file
View file

@ -0,0 +1,152 @@
version: 2
jobs:
install_deps:
working_directory: /go/src/github.com/CityOfZion/neo-go
docker:
- image: vidsyhq/go-builder:latest
environment:
BUILD: false
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
- run: /scripts/build.sh
- save_cache:
key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
paths:
- vendor
- /go/pkg
build_image:
working_directory: /go/src/github.com/CityOfZion/neo-go
docker:
- image: alpine
steps:
- run: apk update && apk add git make curl tar
- checkout
- restore_cache:
keys:
- dependency-cache-{{ .Revision }}
- restore_cache:
keys:
- dependency-cache-cli-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
- setup_remote_docker
- run:
name: Install Docker client
command: |
set -x
VER="17.03.0-ce"
curl -L -o /tmp/docker-$VER.tgz https://get.docker.com/builds/Linux/x86_64/docker-$VER.tgz
tar -xz -C /tmp -f /tmp/docker-$VER.tgz
mv /tmp/docker/* /usr/bin
- run: make build-image
test:
working_directory: /go/src/github.com/CityOfZion/neo-go
docker:
- image: vidsyhq/go-builder:latest
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
- run: make test
vet:
working_directory: /go/src/github.com/CityOfZion/neo-go
docker:
- image: vidsyhq/go-builder:latest
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
- run: make vet
check_version:
working_directory: /go/src/github.com/CityOfZion/neo-go
docker:
- image: vidsyhq/go-builder:latest
steps:
- checkout
- run: make check-version
build_cli:
working_directory: /go/src/github.com/CityOfZion/neo-go
docker:
- image: vidsyhq/go-builder:latest
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
- run: make build
- save_cache:
key: dependency-cache-cli-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
paths:
- bin/neo-go
deploy:
working_directory: /go/src/github.com/CityOfZion/neo-go
docker:
- image: alpine
steps:
- run: apk update && apk add git make curl tar
- checkout
- restore_cache:
key: dependency-cache-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
- restore_cache:
key: dependency-cache-cli-{{ checksum "Gopkg.toml" }}-{{ checksum "VERSION" }}
- setup_remote_docker
- run:
name: Install Docker client
command: |
set -x
VER="17.03.0-ce"
curl -L -o /tmp/docker-$VER.tgz https://get.docker.com/builds/Linux/x86_64/docker-$VER.tgz
tar -xz -C /tmp -f /tmp/docker-$VER.tgz
mv /tmp/docker/* /usr/bin
- run: make build-image
- deploy:
name: deploy
command: make push-to-registry
workflows:
version: 2
workflow:
jobs:
- install_deps:
filters:
tags:
only: /[0-9]+\.[0-9]+\.[0-9]+/
- test:
requires:
- install_deps
filters:
tags:
only: /[0-9]+\.[0-9]+\.[0-9]+/
- vet:
requires:
- install_deps
filters:
tags:
only: /[0-9]+\.[0-9]+\.[0-9]+/
- check_version:
filters:
branches:
ignore: master
- build_cli:
requires:
- install_deps
filters:
tags:
only: /[0-9]+\.[0-9]+\.[0-9]+/
- build_image:
requires:
- install_deps
- build_cli
filters:
tags:
only: /[0-9]+\.[0-9]+\.[0-9]+/
- deploy:
requires:
- build_image
- test
- vet
- check_version
filters:
tags:
only:
- /[0-9]+\.[0-9]+\.[0-9]+/
branches:
ignore: /.*/

5
cli/main.go Normal file
View file

@ -0,0 +1,5 @@
package main
func main() {
}

28
docs/contribution.md Normal file
View file

@ -0,0 +1,28 @@
# Contributions
This document will state guidelines for submitting code.
## Pre-requisites
In order to contribute to Golang, it is essential that you know Golang or language similar to Golang. It is also required that you are familiar with NEO infrastructure.
Reading:
http://docs.neo.org/en-us/network/network-protocol.html
https://tour.golang.org/welcome/1
https://golang.org/doc/effective_go.html#introduction
## 1. Open an Issue Prior
Please open an issue prior to working on the feature, stating the feature you would like to work on.
## 2. Unit test
Unit tests are important, when submitting a PR please ensure that you have at least 70% test coverage.
## 3. Avoid CGo
Due to the complications that come with using CGo, if using it, please provide justification before using it. If using CGo, can provide significant speed boosts and or a large part of the code can be processed in CGo, then we should be able to come to consensus on using it. Generally, it is better to write everything in Golang, so that the code can be maintained by other Gophers.

17
docs/conventions.md Normal file
View file

@ -0,0 +1,17 @@
# Conventions
This document will list conventions that this repo should follow. These are guidelines and if you believe that one should not be followed, then please state why in your PR. If you believe that a piece of code does not follow one of the conventions listed, then please open an issue before making any changes.
When submitting a new convention, please open an issue for discussion, if possible please highlight parts in the code where this convention could help the code readiblity or simplicity.
## Avoid Named return paramters
func example(test int) (num int) {
a = test + 1
num = a * test
return
}
In the above function we have used a named return paramter, which allows you to include a simple return statement without the variables you are returning. This practice can cause confusion when functions become large or the logic becomes complex, so these should be avoided.

12
pkg/Readme.md Normal file
View file

@ -0,0 +1,12 @@
# ReadMe
Currently this package is in Development.
## References
btcd https://github.com/btcsuite/btcd
geth https://github.com/ethereum/go-ethereum
aeternity https://github.com/aeternity/elixir-node

285
pkg/addrmgr/addrmgr.go Normal file
View file

@ -0,0 +1,285 @@
package addrmgr
import (
"errors"
"fmt"
"math/rand"
"sync"
"time"
"github.com/CityOfZion/neo-go/pkg/peer"
"github.com/CityOfZion/neo-go/pkg/wire/payload"
)
const (
maxTries = 5 // Try to connect five times, if we cannot then we say it is bad
// We will keep bad addresses, so that we do not attempt to connect to them in the future.
// nodes at the moment seem to send a large percentage of bad nodes.
maxFailures = 12 // This will be incremented when we connect to a node and for whatever reason, they keep disconnecting.
// The number is high because there could be a period of time, where a node is behaving unrepsonsively and
// we do not want to immmediately put it inside of the bad bucket.
// This is the maximum amount of addresses that the addrmgr will hold
maxAllowedAddrs = 2000
)
type addrStats struct {
tries uint8
failures uint8
permanent bool // permanent = inbound, temp = outbound
lastTried time.Time
lastSuccess time.Time
}
type Addrmgr struct {
addrmtx sync.RWMutex
goodAddrs map[*payload.Net_addr]addrStats
newAddrs map[*payload.Net_addr]struct{}
badAddrs map[*payload.Net_addr]addrStats
knownList map[string]*payload.Net_addr // this contains all of the addresses in badAddrs, newAddrs and goodAddrs
}
func New() *Addrmgr {
return &Addrmgr{
sync.RWMutex{},
make(map[*payload.Net_addr]addrStats, 100),
make(map[*payload.Net_addr]struct{}, 100),
make(map[*payload.Net_addr]addrStats, 100),
make(map[string]*payload.Net_addr, 100),
}
}
//AddAddrs will add new add new addresses into the newaddr list
// This is safe for concurrent access.
func (a *Addrmgr) AddAddrs(newAddrs []*payload.Net_addr) {
newAddrs = removeDuplicates(newAddrs)
var nas []*payload.Net_addr
for _, addr := range newAddrs {
a.addrmtx.Lock()
if _, ok := a.knownList[addr.IPPort()]; !ok { // filter unique addresses
nas = append(nas, addr)
}
a.addrmtx.Unlock()
}
for _, addr := range nas {
a.addrmtx.Lock()
a.newAddrs[addr] = struct{}{}
a.knownList[addr.IPPort()] = addr
a.addrmtx.Unlock()
}
}
// Good returns the good addresses.
// A good address is:
// - Known
// - has been successfully connected to in the last week
// - Note: that while users are launching full nodes from their laptops, the one week marker will need to be modified.
func (a *Addrmgr) Good() []payload.Net_addr {
var goodAddrs []payload.Net_addr
var oneWeekAgo = time.Now().Add(((time.Hour * 24) * 7) * -1)
a.addrmtx.RLock()
// TODO: sort addresses, permanent ones go first
for addr, stat := range a.goodAddrs {
if stat.lastTried.Before(oneWeekAgo) {
continue
}
goodAddrs = append(goodAddrs, *addr)
}
a.addrmtx.RUnlock()
return goodAddrs
}
// Unconnected Addrs are addresses in the newAddr List
func (a *Addrmgr) Unconnected() []payload.Net_addr {
var unconnAddrs []payload.Net_addr
a.addrmtx.RLock()
for addr := range a.newAddrs {
unconnAddrs = append(unconnAddrs, *addr)
}
a.addrmtx.RUnlock()
return unconnAddrs
}
// Bad Addrs are addresses in the badAddr List
// They are put there if we have tried to connect
// to them in the past and it failed.
func (a *Addrmgr) Bad() []payload.Net_addr {
var badAddrs []payload.Net_addr
a.addrmtx.RLock()
for addr := range a.badAddrs {
badAddrs = append(badAddrs, *addr)
}
a.addrmtx.RUnlock()
return badAddrs
}
// FetchMoreAddresses will return true if the numOfKnownAddrs are less than 100
// This number is kept low because at the moment, there are not a lot of Good Addresses
// Tests have shown that at most there are < 100
func (a *Addrmgr) FetchMoreAddresses() bool {
var numOfKnownAddrs int
a.addrmtx.RLock()
numOfKnownAddrs = len(a.knownList)
a.addrmtx.RUnlock()
return numOfKnownAddrs < maxAllowedAddrs
}
// ConnectionComplete will be called by the server when we have done the handshake
// and Not when we have successfully Connected with net.Conn
// It is to tell the AddrMgr that we have connected to a peer
// This peer should already be known to the AddrMgr because
// We send it to the Connmgr
func (a *Addrmgr) ConnectionComplete(addressport string, inbound bool) {
a.addrmtx.Lock()
defer a.addrmtx.Unlock()
// if addrmgr does not know this, then we just return
if _, ok := a.knownList[addressport]; !ok {
fmt.Println("Connected to an unknown address:port ", addressport)
return
}
na := a.knownList[addressport]
// move it from newAddrs List to GoodAddr List
stats := a.goodAddrs[na]
stats.lastSuccess = time.Now()
stats.lastTried = time.Now()
stats.permanent = inbound
stats.tries++
a.goodAddrs[na] = stats
// remove it from new and bad, if it was there
delete(a.newAddrs, na)
delete(a.badAddrs, na)
// Unfortunately, Will have a lot of bad nodes because of people connecting to nodes
// from their laptop. TODO Sort function in good will mitigate.
}
// Failed will be called by ConnMgr
// It is used to tell the AddrMgr that they had tried connecting an address an have failed
// This is concurrent safe
func (a *Addrmgr) Failed(addressport string) {
a.addrmtx.Lock()
defer a.addrmtx.Unlock()
// if addrmgr does not know this, then we just return
if _, ok := a.knownList[addressport]; !ok {
fmt.Println("Connected to an unknown address:port ", addressport)
return
}
na := a.knownList[addressport]
// HMM: logic here could be simpler if we make it one list instead
if stats, ok := a.badAddrs[na]; ok {
stats.lastTried = time.Now()
stats.failures++
stats.tries++
if float32(stats.failures)/float32(stats.tries) > 0.8 && stats.tries > 5 {
delete(a.badAddrs, na)
return
}
a.badAddrs[na] = stats
return
}
if stats, ok := a.goodAddrs[na]; ok {
fmt.Println("We have a good addr", na.IPPort())
stats.lastTried = time.Now()
stats.failures++
stats.tries++
if float32(stats.failures)/float32(stats.tries) > 0.5 && stats.tries > 10 {
delete(a.goodAddrs, na)
a.badAddrs[na] = stats
return
}
a.goodAddrs[na] = stats
return
}
if _, ok := a.newAddrs[na]; ok {
delete(a.newAddrs, na)
a.badAddrs[na] = addrStats{}
}
}
//OnAddr is the responder for the Config file
// when a OnAddr is received by a peer
func (a *Addrmgr) OnAddr(p *peer.Peer, msg *payload.AddrMessage) {
a.AddAddrs(msg.AddrList)
}
// OnGetAddr Is called when a peer sends a request for the addressList
// We will give them the best addresses we have from good.
func (a *Addrmgr) OnGetAddr(p *peer.Peer, msg *payload.GetAddrMessage) {
a.addrmtx.RLock()
defer a.addrmtx.RUnlock()
// Push most recent peers to peer
addrMsg, err := payload.NewAddrMessage()
if err != nil {
return
}
for _, add := range a.Good() {
addrMsg.AddNetAddr(&add)
}
p.Write(addrMsg)
}
// NewAddr will return an address for the external caller to
// connect to. In our case, it will be the connection manager.
func (a *Addrmgr) NewAddr() (string, error) {
// For now it just returns a random value from unconnected
// TODO: When an address is tried, the address manager is notified.
// When asked for a new address, this should be taken into account
// when choosing a new one, also the number of retries.
unconnected := a.Unconnected()
if len(unconnected) == 0 {
return "", errors.New("No Addresses to give")
}
rand := rand.Intn(len(unconnected))
return unconnected[rand].IPPort(), nil
}
// https://www.dotnetperls.com/duplicates-go
func removeDuplicates(elements []*payload.Net_addr) []*payload.Net_addr {
encountered := map[string]bool{}
result := []*payload.Net_addr{}
for _, element := range elements {
if encountered[element.IPPort()] == true {
// Do not add duplicate.
} else {
// Record this element as an encountered element.
encountered[element.IPPort()] = true
// Append to result slice.
result = append(result, element)
}
}
// Return the new slice.
return result
}

167
pkg/addrmgr/addrmgr_test.go Normal file
View file

@ -0,0 +1,167 @@
package addrmgr_test
import (
"crypto/rand"
"testing"
"github.com/CityOfZion/neo-go/pkg/wire/payload"
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
"github.com/CityOfZion/neo-go/pkg/addrmgr"
"github.com/stretchr/testify/assert"
)
func TestNewAddrs(t *testing.T) {
addrmgr := addrmgr.New()
assert.NotEqual(t, nil, addrmgr)
}
func TestAddAddrs(t *testing.T) {
addrmgr := addrmgr.New()
ip := [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
addr, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService)
ip = [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // same
addr2, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService)
ip = [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
addr3, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService)
addrs := []*payload.Net_addr{addr, addr2, addr, addr3}
addrmgr.AddAddrs(addrs)
assert.Equal(t, 2, len(addrmgr.Unconnected()))
assert.Equal(t, 0, len(addrmgr.Good()))
assert.Equal(t, 0, len(addrmgr.Bad()))
}
func TestFetchMoreAddress(t *testing.T) {
addrmgr := addrmgr.New()
addrs := []*payload.Net_addr{}
ip := make([]byte, 16)
for i := 0; i <= 2000; i++ { // Add more than maxAllowedAddrs
rand.Read(ip)
var nip [16]byte
copy(nip[:], ip[:16])
addr, _ := payload.NewNetAddr(0, nip, 1033, protocol.NodePeerService)
addrs = append(addrs, addr)
}
addrmgr.AddAddrs(addrs)
assert.Equal(t, false, addrmgr.FetchMoreAddresses())
}
func TestConnComplete(t *testing.T) {
addrmgr := addrmgr.New()
ip := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
addr, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService)
ip2 := [16]byte{2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
addr2, _ := payload.NewNetAddr(0, ip2, 1033, protocol.NodePeerService)
ip3 := [16]byte{3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
addr3, _ := payload.NewNetAddr(0, ip3, 1033, protocol.NodePeerService)
addrs := []*payload.Net_addr{addr, addr2, addr3}
addrmgr.AddAddrs(addrs)
assert.Equal(t, len(addrs), len(addrmgr.Unconnected()))
// a successful connection
addrmgr.ConnectionComplete(addr.IPPort(), true)
addrmgr.ConnectionComplete(addr.IPPort(), true) // should have no change
assert.Equal(t, len(addrs)-1, len(addrmgr.Unconnected()))
assert.Equal(t, 1, len(addrmgr.Good()))
// another successful connection
addrmgr.ConnectionComplete(addr2.IPPort(), true)
assert.Equal(t, len(addrs)-2, len(addrmgr.Unconnected()))
assert.Equal(t, 2, len(addrmgr.Good()))
}
func TestAttempted(t *testing.T) {
addrmgr := addrmgr.New()
ip := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
addr, _ := payload.NewNetAddr(0, ip, 1033, protocol.NodePeerService)
addrs := []*payload.Net_addr{addr}
addrmgr.AddAddrs(addrs)
addrmgr.Failed(addr.IPPort())
assert.Equal(t, 1, len(addrmgr.Bad())) // newAddrs was attmepted and failed. Move to Bad
}
func TestAttemptedMoveFromGoodToBad(t *testing.T) {
addrmgr := addrmgr.New()
ip := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
addr, _ := payload.NewNetAddr(0, ip, 1043, protocol.NodePeerService)
addrs := []*payload.Net_addr{addr}
addrmgr.AddAddrs(addrs)
addrmgr.ConnectionComplete(addr.IPPort(), true)
addrmgr.ConnectionComplete(addr.IPPort(), true)
addrmgr.ConnectionComplete(addr.IPPort(), true)
assert.Equal(t, 1, len(addrmgr.Good()))
addrmgr.Failed(addr.IPPort())
addrmgr.Failed(addr.IPPort())
addrmgr.Failed(addr.IPPort())
addrmgr.Failed(addr.IPPort())
addrmgr.Failed(addr.IPPort())
addrmgr.Failed(addr.IPPort())
addrmgr.Failed(addr.IPPort())
addrmgr.Failed(addr.IPPort())
addrmgr.Failed(addr.IPPort())
// over threshhold, and will be classed as a badAddr L251
assert.Equal(t, 0, len(addrmgr.Good()))
assert.Equal(t, 1, len(addrmgr.Bad()))
}
func TestGetAddress(t *testing.T) {
addrmgr := addrmgr.New()
ip := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
addr, _ := payload.NewNetAddr(0, ip, 10333, protocol.NodePeerService)
ip2 := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
addr2, _ := payload.NewNetAddr(0, ip2, 10334, protocol.NodePeerService)
ip3 := [16]byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // different
addr3, _ := payload.NewNetAddr(0, ip3, 10335, protocol.NodePeerService)
addrs := []*payload.Net_addr{addr, addr2, addr3}
addrmgr.AddAddrs(addrs)
fetchAddr, err := addrmgr.NewAddr()
assert.Equal(t, nil, err)
ipports := []string{addr.IPPort(), addr2.IPPort(), addr3.IPPort()}
assert.Contains(t, ipports, fetchAddr)
}

0
pkg/addrmgr/peers.json Normal file
View file

23
pkg/addrmgr/readme.md Normal file
View file

@ -0,0 +1,23 @@
# Package - Address Manager
This package can be used as a standalone to manage addresses on the NEO network. Although you can use it for other chains, the config parameters have been modified for the state of NEO as of now.
## Responsibility
To manage the data that the node knows about addresses; data on good addresses, retry rates, failures, and lastSuccessful connection will be managed by the address manager. Also, If a service wants to fetch good address then it will be asked for from the address manager.
## Features
- On GetAddr it will give a list of good addresses to connect to
- On Addr it will receive addresses and remove any duplicates.
- General Management of Addresses
- Periodically saves the peers and metadata about peer into a .json file for retrieval (Not implemented yet)
## Note
The Address manager will not deal with making connections to nodes. Please check the tests for the use cases for this package.

View file

@ -0,0 +1,187 @@
package blockchain
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"github.com/CityOfZion/neo-go/pkg/chainparams"
"github.com/CityOfZion/neo-go/pkg/database"
"github.com/CityOfZion/neo-go/pkg/wire/payload"
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
)
var (
ErrBlockValidation = errors.New("Block failed sanity check")
ErrBlockVerification = errors.New("Block failed to be consistent with the current blockchain")
)
// Blockchain holds the state of the chain
type Chain struct {
db *database.LDB
net protocol.Magic
}
func New(db *database.LDB, net protocol.Magic) *Chain {
marker := []byte("HasBeenInitialisedAlready")
_, err := db.Get(marker)
if err != nil {
// This is a new db
fmt.Println("New Database initialisation")
db.Put(marker, []byte{})
// We add the genesis block into the db
// along with the indexes for it
if net == protocol.MainNet {
genesisBlock, err := hex.DecodeString(chainparams.GenesisBlock)
if err != nil {
fmt.Println("Could not add genesis header into db")
db.Delete(marker)
return nil
}
r := bytes.NewReader(genesisBlock)
b := payload.Block{}
err = b.Decode(r)
if err != nil {
fmt.Println("could not Decode genesis block")
db.Delete(marker)
return nil
}
err = db.AddHeader(&b.BlockBase)
if err != nil {
fmt.Println("Could not add genesis header")
db.Delete(marker)
return nil
}
err = db.AddTransactions(b.Hash, b.Txs)
if err != nil {
fmt.Println("Could not add Genesis Transactions")
db.Delete(marker)
return nil
}
}
if net == protocol.TestNet {
fmt.Println("TODO: Setup the genesisBlock for TestNet")
return nil
}
}
return &Chain{
db,
net,
}
}
func (c *Chain) AddBlock(msg *payload.BlockMessage) error {
if !validateBlock(msg) {
return ErrBlockValidation
}
if !c.verifyBlock(msg) {
return ErrBlockVerification
}
fmt.Println("Block Hash is ", msg.Hash.String())
buf := new(bytes.Buffer)
err := msg.Encode(buf)
if err != nil {
return err
}
return c.db.Put(msg.Hash.Bytes(), buf.Bytes())
}
// validateBlock will check the transactions,
// merkleroot is good, signature is good,every that does not require state
// This may be moved to the syncmanager. This function should not be done in a seperate go-routine
// We are intentionally blocking here because if the block is invalid, we will
// disconnect from the peer.
// We could have this return an error instead; where the error could even
// say where the validation failed, for the logs.
func validateBlock(msg *payload.BlockMessage) bool {
return true
}
func (c *Chain) verifyBlock(msg *payload.BlockMessage) bool {
return true
}
// This will add a header into the db,
// indexing it also, this method will not
// run any checks, like if it links with a header
// previously in the db
// func (c *Chain) addHeaderNoCheck(header *payload.BlockBase) error {
// }
//addHeaders is not safe for concurrent access
func (c *Chain) ValidateHeaders(msg *payload.HeadersMessage) error {
table := database.NewTable(c.db, database.HEADER)
latestHash, err := table.Get(database.LATESTHEADER)
if err != nil {
return err
}
key := latestHash
val, err := table.Get(key)
lastHeader := &payload.BlockBase{}
err = lastHeader.Decode(bytes.NewReader(val))
if err != nil {
return err
}
// TODO?:Maybe we should sort these headers using the Index
// We should not get them in mixed order, but doing it would not be expensive
// If they are already in order
// Do checks on headers
for _, currentHeader := range msg.Headers {
if lastHeader == nil {
// This should not happen as genesis header is added if new
// database, however we check nonetheless
return errors.New("Previous Header is nil")
}
// Check current hash links with previous
if currentHeader.PrevHash != lastHeader.Hash {
return errors.New("Last Header hash != current header Prev hash")
}
// Check current Index is one more than the previous Index
if currentHeader.Index != lastHeader.Index+1 {
return errors.New("Last Header Index != current header Index")
}
// Check current timestamp is more than the previous header's timestamp
if lastHeader.Timestamp > currentHeader.Timestamp {
return errors.New("Timestamp of Previous Header is more than Timestamp of current Header")
}
// NONONO:Do not check if current is more than 15 secs in future
// some blocks had delay from forks in past.
// NOTE: These are the only non-contextual checks we can do without the blockchain state
lastHeader = currentHeader
}
return nil
}
func (c *Chain) AddHeaders(msg *payload.HeadersMessage) error {
for _, header := range msg.Headers {
if err := c.db.AddHeader(header); err != nil {
return err
}
}
return nil
}

7
pkg/chainparams/asset.go Normal file
View file

@ -0,0 +1,7 @@
package chainparams
// GAS is the scripthash native neo asset: GAS
var GAS = "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"
// NEO is the scripthash native neo asset: NEO
var NEO = "c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b"

51
pkg/chainparams/config.go Normal file
View file

@ -0,0 +1,51 @@
package chainparams
// For now we will just use this to store the
// peers, to test peer data
// Once complete, it will store the genesis params for testnet and mainnet
var mainnetSeedList = []string{
"seed1.neo.org:10333", // NOP
"seed2.neo.org:10333", // YEP
"seed3.neo.org:10333", // YEP
"seed4.neo.org:10333", // NOP
"seed5.neo.org:10333", // YEP
"13.59.52.94:10333", // NOP
"18.220.214.143:10333", // NOP
"13.58.198.112:10333", // NOP
"13.59.14.206:10333", // NOP
"18.216.9.7:10333", // NOP
}
//MainnetSeedList is a string slice containing the initial seeds from protocol.mainnet
// That are replying
var MainnetSeedList = []string{
"seed2.neo.org:10333",
"seed3.neo.org:10333",
"seed5.neo.org:10333",
"http://seed1.ngd.network:10333",
"http://seed2.ngd.network:10333",
"http://seed3.ngd.network:10333",
"http://seed4.ngd.network:10333",
"http://seed5.ngd.network:10333",
"http://seed6.ngd.network:10333",
"http://seed7.ngd.network:10333",
"http://seed8.ngd.network:10333",
"http://seed9.ngd.network:10333",
"http://seed10.ngd.network:10333",
}
var testNetSeedList = []string{
"18.218.97.227:20333",
"18.219.30.120:20333",
"18.219.13.91:20333",
"13.59.116.121:20333",
"18.218.255.178:20333",
}
// GenesisHash for mainnet
var GenesisHash = "d42561e3d30e15be6400b6df2f328e02d2bf6354c41dce433bc57687c82144bf"
// GenesisBlock for mainnet
var GenesisBlock = "000000000000000000000000000000000000000000000000000000000000000000000000f41bc036e39b0d6b0579c851c6fde83af802fa4e57bec0bc3365eae3abf43f8065fc8857000000001dac2b7c0000000059e75d652b5d3827bf04c165bbe9ef95cca4bf55010001510400001dac2b7c00000000400000455b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e882a1227d2c7b226c616e67223a22656e222c226e616d65223a22416e745368617265227d5d0000c16ff28623000000da1745e9b549bd0bfa1a569971c77eba30cd5a4b00000000400001445b7b226c616e67223a227a682d434e222c226e616d65223a22e5b08fe89a81e5b881227d2c7b226c616e67223a22656e222c226e616d65223a22416e74436f696e227d5d0000c16ff286230008009f7fd096d37ed2c0e3f7f0cfc924beef4ffceb680000000001000000019b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc50000c16ff28623005fa99d93303775fe50ca119c327759313eccfa1c01000151"

25
pkg/connmgr/config.go Normal file
View file

@ -0,0 +1,25 @@
package connmgr
import (
"net"
)
// Config contains all methods which will be set by the caller to setup the connection manager.
type Config struct {
// GetAddress will return a single address for the connection manager to connect to
GetAddress func() (string, error)
// OnConnection is called by the connection manager when
// we successfully connect to a peer
// The caller should ideally inform the address manager that we have connected to this address in this function
OnConnection func(conn net.Conn, addr string)
// OnAccept will take a established connection
OnAccept func(net.Conn)
// Port is the port in the format "10333"
Port string
// DialTimeout is the amount of time, before we can disconnect a pending dialed connection
DialTimeout int
}

272
pkg/connmgr/connmgr.go Normal file
View file

@ -0,0 +1,272 @@
package connmgr
import (
"errors"
"fmt"
"net"
"net/http"
"time"
"github.com/CityOfZion/neo-go/pkg/wire/util/ip"
)
var (
// maxOutboundConn is the maximum number of active peers
// that the connection manager will try to have
maxOutboundConn = 10
// maxRetries is the maximum amount of successive retries that
// we can have before we stop dialing that peer
maxRetries = uint8(5)
)
// Connmgr manages pending/active/failed cnnections
type Connmgr struct {
config Config
PendingList map[string]*Request
ConnectedList map[string]*Request
actionch chan func()
}
//New creates a new connection manager
func New(cfg Config) *Connmgr {
cnnmgr := &Connmgr{
cfg,
make(map[string]*Request),
make(map[string]*Request),
make(chan func(), 300),
}
go func() {
ip := iputils.GetLocalIP()
addrPort := ip.String() + ":" + cfg.Port
listener, err := net.Listen("tcp", addrPort)
if err != nil {
fmt.Println("Error connecting to outbound ", err)
}
defer func() {
listener.Close()
}()
for {
conn, err := listener.Accept()
if err != nil {
continue
}
// TODO(kev): in the OnAccept the connection address will be added to AddrMgr
go cfg.OnAccept(conn)
}
}()
return cnnmgr
}
// NewRequest will make a new connection
// Gets the address from address func in config
// Then dials it and assigns it to pending
func (c *Connmgr) NewRequest() {
// Fetch address
addr, err := c.config.GetAddress()
if err != nil {
fmt.Println("Error getting address", err)
}
// empty request item
r := &Request{}
r.Addr = addr
fmt.Println("Connecting")
c.Connect(r)
}
func (c *Connmgr) Connect(r *Request) error {
r.Retries++
conn, err := c.Dial(r.Addr)
if err != nil {
c.failed(r)
return err
}
r.Conn = conn
r.Inbound = true
// r.Permanent is set by the caller. default is false
// The permanent connections will be the ones that are hardcoded, e.g seed3.ngd.network
return c.connected(r)
}
func (cm *Connmgr) Disconnect(addr string) {
// fetch from connected list
r, ok := cm.ConnectedList[addr]
if !ok {
// If not in connected, check pending
r, ok = cm.PendingList[addr]
}
cm.disconnected(r)
}
// Dial is used to dial up connections given the addres and ip in the form address:port
func (c *Connmgr) Dial(addr string) (net.Conn, error) {
dialTimeout := 1 * time.Second
conn, err := net.DialTimeout("tcp", addr, dialTimeout)
if err != nil {
if !isConnected() {
return nil, errors.New("Fatal Error: You do not seem to be connected to the internet")
}
return conn, err
}
return conn, nil
}
func (cm *Connmgr) failed(r *Request) {
cm.actionch <- func() {
// priority to check if it is permanent or inbound
// if so then these peers are valuable in NEO and so we will just retry another time
if r.Inbound || r.Permanent {
multiplier := time.Duration(r.Retries * 10)
time.AfterFunc(multiplier*time.Second,
func() {
cm.Connect(r)
},
)
// if not then we should check if this request has had maxRetries
// if it has then get a new address
// if not then call Connect on it again
} else if r.Retries > maxRetries {
if cm.config.GetAddress != nil {
go cm.NewRequest()
}
fmt.Println("This peer has been tried the maximum amount of times and a source of new address has not been specified.")
} else {
go cm.Connect(r)
}
}
}
// Disconnected is called when a peer disconnects.
// we take the addr from peer, which is also it's key in the map
// and we use it to remove it from the connectedList
func (c *Connmgr) disconnected(r *Request) error {
errChan := make(chan error, 0)
c.actionch <- func() {
var err error
if r == nil {
err = errors.New("Request object is nil")
}
r2 := *r // dereference it, so that r.Addr is not lost on delete
// if for some reason the underlying connection is not closed, close it
r.Conn.Close()
r.Conn = nil
// if for some reason it is in pending list, remove it
delete(c.PendingList, r.Addr)
delete(c.ConnectedList, r.Addr)
c.failed(&r2)
errChan <- err
}
return <-errChan
}
//Connected is called when the connection manager
// makes a successful connection.
func (c *Connmgr) connected(r *Request) error {
errorChan := make(chan error, 0)
c.actionch <- func() {
var err error
// This should not be the case, since we connected
// Keeping it here to be safe
if r == nil {
err = errors.New("Request object as nil inside of the connected function")
}
// reset retries to 0
r.Retries = 0
// add to connectedList
c.ConnectedList[r.Addr] = r
// remove from pending if it was there
delete(c.PendingList, r.Addr)
if c.config.OnConnection != nil {
c.config.OnConnection(r.Conn, r.Addr)
}
fmt.Println("Error connected", err)
errorChan <- err
}
return <-errorChan
}
// Pending is synchronous, we do not want to continue with logic
// until we are certain it has been added to the pendingList
func (c *Connmgr) pending(r *Request) error {
errChan := make(chan error, 0)
c.actionch <- func() {
var err error
if r == nil {
err = errors.New("Error : Request object is nil")
}
c.PendingList[r.Addr] = r
errChan <- err
}
return <-errChan
}
func (c *Connmgr) Run() {
go c.loop()
}
func (c *Connmgr) loop() {
for {
select {
case f := <-c.actionch:
f()
}
}
}
// https://stackoverflow.com/questions/50056144/check-for-internet-connection-from-application
func isConnected() (ok bool) {
_, err := http.Get("http://clients3.google.com/generate_204")
if err != nil {
return false
}
return true
}

106
pkg/connmgr/connmgr_test.go Normal file
View file

@ -0,0 +1,106 @@
package connmgr_test
import (
"testing"
"github.com/CityOfZion/neo-go/pkg/connmgr"
"github.com/stretchr/testify/assert"
)
func TestDial(t *testing.T) {
cfg := connmgr.Config{
GetAddress: nil,
OnConnection: nil,
OnAccept: nil,
Port: "",
DialTimeout: 0,
}
cm := connmgr.New(cfg)
cm.Run()
ipport := "google.com:80" // google unlikely to go offline, a better approach to test Dialing is welcome.
conn, err := cm.Dial(ipport)
assert.Equal(t, nil, err)
assert.NotEqual(t, nil, conn)
}
func TestConnect(t *testing.T) {
cfg := connmgr.Config{
GetAddress: nil,
OnConnection: nil,
OnAccept: nil,
Port: "",
DialTimeout: 0,
}
cm := connmgr.New(cfg)
cm.Run()
ipport := "google.com:80"
r := connmgr.Request{Addr: ipport}
cm.Connect(&r)
assert.Equal(t, 1, len(cm.ConnectedList))
}
func TestNewRequest(t *testing.T) {
address := "google.com:80"
var getAddr = func() (string, error) {
return address, nil
}
cfg := connmgr.Config{
GetAddress: getAddr,
OnConnection: nil,
OnAccept: nil,
Port: "",
DialTimeout: 0,
}
cm := connmgr.New(cfg)
cm.Run()
cm.NewRequest()
if _, ok := cm.ConnectedList[address]; ok {
assert.Equal(t, true, ok)
assert.Equal(t, 1, len(cm.ConnectedList))
return
}
assert.Fail(t, "Could not find the address in the connected lists")
}
func TestDisconnect(t *testing.T) {
address := "google.com:80"
var getAddr = func() (string, error) {
return address, nil
}
cfg := connmgr.Config{
GetAddress: getAddr,
OnConnection: nil,
OnAccept: nil,
Port: "",
DialTimeout: 0,
}
cm := connmgr.New(cfg)
cm.Run()
cm.NewRequest()
cm.Disconnect(address)
assert.Equal(t, 0, len(cm.ConnectedList))
}

26
pkg/connmgr/readme.md Normal file
View file

@ -0,0 +1,26 @@
# Package - Connection Manager
## Responsibility
- Manages the active, failed and pending connections for the node.
## Features
- Takes an address, dials it and packages it into a request to manage.
- Retry failed connections.
- Uses one function as a source for it's addresses. It does not manage addresses.
## Usage
The following methods are exposed from the Connection manager:
- NewRequest() : This will fetch a new address and connect to it.
- Connect(r *Request) : This takes a Request object and connects to it. It follow the same logic as NewRequest() however instead of getting the address from the datasource given upon initialisation, you directly feed the address you want to connect to.
- Disconnect(addrport string) : Given an address:port, this will disconnect it, close the connection and remove it from the connected and pending list, if it was there.
- Dial(addrport string) (net.Conn, error) : Given an address:port, this will connect to it and return a pointer to a connection plus a nil error if successful, or nil with an error.

13
pkg/connmgr/request.go Normal file
View file

@ -0,0 +1,13 @@
package connmgr
import (
"net"
)
type Request struct {
Conn net.Conn
Addr string
Permanent bool
Inbound bool
Retries uint8 // should not be trying more than 255 tries
}

93
pkg/crypto/aes/aes256.go Executable file
View file

@ -0,0 +1,93 @@
package aes
import (
"crypto/aes"
"crypto/cipher"
)
// AESEncrypt encrypts the key with the given source.
func Encrypt(src, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
ecb := newECBEncrypter(block)
out := make([]byte, len(src))
ecb.CryptBlocks(out, src)
return out, nil
}
// AESDecrypt decrypts the encrypted source with the given key.
func Decrypt(crypted, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockMode := newECBDecrypter(block)
out := make([]byte, len(crypted))
blockMode.CryptBlocks(out, crypted)
return out, nil
}
type ecb struct {
b cipher.Block
blockSize int
}
func newECB(b cipher.Block) *ecb {
return &ecb{
b: b,
blockSize: b.BlockSize(),
}
}
type ecbEncrypter ecb
func newECBEncrypter(b cipher.Block) cipher.BlockMode {
return (*ecbEncrypter)(newECB(b))
}
func (ecb *ecbEncrypter) BlockSize() int {
return ecb.blockSize
}
func (ecb *ecbEncrypter) CryptBlocks(dst, src []byte) {
if len(src)%ecb.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
ecb.b.Encrypt(dst, src[:ecb.blockSize])
src = src[ecb.blockSize:]
dst = dst[ecb.blockSize:]
}
}
type ecbDecrypter ecb
func newECBDecrypter(b cipher.Block) cipher.BlockMode {
return (*ecbDecrypter)(newECB(b))
}
func (ecb ecbDecrypter) BlockSize() int {
return ecb.blockSize
}
func (ecb *ecbDecrypter) CryptBlocks(dst, src []byte) {
if len(src)%ecb.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
ecb.b.Decrypt(dst, src[:ecb.blockSize])
src = src[ecb.blockSize:]
dst = dst[ecb.blockSize:]
}
}

78
pkg/crypto/base58/base58.go Executable file
View file

@ -0,0 +1,78 @@
package base58
import (
"fmt"
"math/big"
)
const prefix rune = '1'
var decodeMap = map[rune]int64{
'1': 0, '2': 1, '3': 2, '4': 3, '5': 4,
'6': 5, '7': 6, '8': 7, '9': 8, 'A': 9,
'B': 10, 'C': 11, 'D': 12, 'E': 13, 'F': 14,
'G': 15, 'H': 16, 'J': 17, 'K': 18, 'L': 19,
'M': 20, 'N': 21, 'P': 22, 'Q': 23, 'R': 24,
'S': 25, 'T': 26, 'U': 27, 'V': 28, 'W': 29,
'X': 30, 'Y': 31, 'Z': 32, 'a': 33, 'b': 34,
'c': 35, 'd': 36, 'e': 37, 'f': 38, 'g': 39,
'h': 40, 'i': 41, 'j': 42, 'k': 43, 'm': 44,
'n': 45, 'o': 46, 'p': 47, 'q': 48, 'r': 49,
's': 50, 't': 51, 'u': 52, 'v': 53, 'w': 54,
'x': 55, 'y': 56, 'z': 57,
}
// Base58Decode decodes the base58 encoded string.
func Decode(s string) ([]byte, error) {
var (
startIndex = 0
zero = 0
)
for i, c := range s {
if c == prefix {
zero++
} else {
startIndex = i
break
}
}
var (
n = big.NewInt(0)
div = big.NewInt(58)
)
for _, c := range s[startIndex:] {
charIndex, ok := decodeMap[c]
if !ok {
return nil, fmt.Errorf(
"invalid character '%c' when decoding this base58 string: '%s'", c, s,
)
}
n.Add(n.Mul(n, div), big.NewInt(charIndex))
}
out := n.Bytes()
buf := make([]byte, (zero + len(out)))
copy(buf[zero:], out[:])
return buf, nil
}
// Base58Encode encodes a byte slice to be a base58 encoded string.
func Encode(bytes []byte) string {
var (
lookupTable = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
x = new(big.Int).SetBytes(bytes)
r = new(big.Int)
m = big.NewInt(58)
zero = big.NewInt(0)
encoded string
)
for x.Cmp(zero) > 0 {
x.QuoRem(x, m, r)
encoded = string(lookupTable[r.Int64()]) + encoded
}
return encoded
}

View file

@ -0,0 +1,32 @@
package base58
import (
"encoding/hex"
"testing"
"github.com/stretchr/testify/assert"
)
func TestDecode(t *testing.T) {
input := "1F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX"
data, err := Decode(input)
if err != nil {
t.Fatal(err)
}
expected := "0099bc78ba577a95a11f1a344d4d2ae55f2f857b989ea5e5e2"
actual := hex.EncodeToString(data)
assert.Equal(t, expected, actual)
}
func TestEncode(t *testing.T) {
input := "0099bc78ba577a95a11f1a344d4d2ae55f2f857b989ea5e5e2"
inputBytes, _ := hex.DecodeString(input)
data := Encode(inputBytes)
expected := "F1tAaz5x1HUXrCNLbtMDqcw6o5GNn4xqX" // Removed the 1 as it is not checkEncoding
actual := data
assert.Equal(t, expected, actual)
}

18
pkg/crypto/elliptic/Readme.md Executable file
View file

@ -0,0 +1,18 @@
## Package - Elliptic
### Why
The curve and arithmetic functions have been modularised, so that curves can be swapped in and out, without effecting the functionality.
The modular arithmetic used is not specialised for a specific curve.
In order to use this package, you must declare an ellipticcurve struct and then set the curve.
Example:
`
curve = NewEllipticCurve(Secp256k1)
`
If no curve is set, the default curve is the r1 curve used for NEO. The tests are done using the k1 curve, so in the elliptic_test.go file, the curve is changed accordingly.

58
pkg/crypto/elliptic/curves.go Executable file
View file

@ -0,0 +1,58 @@
package elliptic
/*
This file was originally made by vsergeev.
Modifications have been made under the MIT license.
License: MIT
*/
import (
"math/big"
)
var curve Curve
type curveType string
const (
Secp256r1 curveType = "Secp256r1"
Secp256k1 curveType = "Secp256k1"
)
func (ChosenCurve *Curve) SetCurveSecp256r1() {
ChosenCurve.P, _ = new(big.Int).SetString("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16) //Q
ChosenCurve.A, _ = new(big.Int).SetString("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16)
ChosenCurve.B, _ = new(big.Int).SetString("5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", 16)
ChosenCurve.G.X, _ = new(big.Int).SetString("6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", 16)
ChosenCurve.G.Y, _ = new(big.Int).SetString("4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5", 16)
ChosenCurve.N, _ = new(big.Int).SetString("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", 16)
ChosenCurve.H, _ = new(big.Int).SetString("01", 16)
ChosenCurve.Name = "Secp256r1"
}
func (ChosenCurve *Curve) SetCurveSecp256k1() {
ChosenCurve.P, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16)
ChosenCurve.A, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000000", 16)
ChosenCurve.B, _ = new(big.Int).SetString("0000000000000000000000000000000000000000000000000000000000000007", 16)
ChosenCurve.G.X, _ = new(big.Int).SetString("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16)
ChosenCurve.G.Y, _ = new(big.Int).SetString("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16)
ChosenCurve.N, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16)
ChosenCurve.H, _ = new(big.Int).SetString("01", 16)
ChosenCurve.Name = "Secp256k1"
}
func NewEllipticCurve(ct curveType) Curve {
var curve Curve
switch ct {
case Secp256k1:
curve.SetCurveSecp256k1()
case Secp256r1:
curve.SetCurveSecp256r1()
default:
curve.SetCurveSecp256r1()
}
return curve
}

317
pkg/crypto/elliptic/elliptic.go Executable file
View file

@ -0,0 +1,317 @@
/*
This file has been modified under the MIT license.
Original: https://github.com/vsergeev/btckeygenie
*/
package elliptic
import (
nativeelliptic "crypto/elliptic"
"encoding/hex"
"errors"
"fmt"
"math/big"
)
// Point represents a point on an EllipticCurve.
type Point struct {
X *big.Int
Y *big.Int
}
/* y**2 = x**3 + a*x + b % p */
// Curve represents the parameters of a short Weierstrass equation elliptic curve.
type Curve struct {
A *big.Int
B *big.Int
P *big.Int
G Point
N *big.Int
H *big.Int
Name string
}
// dump dumps the bytes of a point for debugging.
func (p *Point) dump() {
fmt.Print(p.format())
}
// format formats the bytes of a point for debugging.
func (p *Point) format() string {
if p.X == nil && p.Y == nil {
return "(inf,inf)"
}
return fmt.Sprintf("(%s,%s)", hex.EncodeToString(p.X.Bytes()), hex.EncodeToString(p.Y.Bytes()))
}
func (ec Curve) Params() *nativeelliptic.CurveParams {
return &nativeelliptic.CurveParams{
P: ec.P,
N: ec.N,
B: ec.B,
Gx: ec.G.X,
Gy: ec.G.Y,
BitSize: 256,
Name: ec.Name,
}
}
/*** Modular Arithmetic ***/
/* NOTE: Returning a new z each time below is very space inefficient, but the
* alternate accumulator based design makes the point arithmetic functions look
* absolutely hideous. I may still change this in the future. */
// addMod computes z = (x + y) % p.
func addMod(x *big.Int, y *big.Int, p *big.Int) (z *big.Int) {
z = new(big.Int).Add(x, y)
z.Mod(z, p)
return z
}
// subMod computes z = (x - y) % p.
func subMod(x *big.Int, y *big.Int, p *big.Int) (z *big.Int) {
z = new(big.Int).Sub(x, y)
z.Mod(z, p)
return z
}
// mulMod computes z = (x * y) % p.
func mulMod(x *big.Int, y *big.Int, p *big.Int) (z *big.Int) {
n := new(big.Int).Set(x)
z = big.NewInt(0)
for i := 0; i < y.BitLen(); i++ {
if y.Bit(i) == 1 {
z = addMod(z, n, p)
}
n = addMod(n, n, p)
}
return z
}
// invMod computes z = (1/x) % p.
func invMod(x *big.Int, p *big.Int) (z *big.Int) {
z = new(big.Int).ModInverse(x, p)
return z
}
// expMod computes z = (x^e) % p.
func expMod(x *big.Int, y *big.Int, p *big.Int) (z *big.Int) {
z = new(big.Int).Exp(x, y, p)
return z
}
// sqrtMod computes z = sqrt(x) % p.
func sqrtMod(x *big.Int, p *big.Int) (z *big.Int) {
/* assert that p % 4 == 3 */
if new(big.Int).Mod(p, big.NewInt(4)).Cmp(big.NewInt(3)) != 0 {
panic("p is not equal to 3 mod 4!")
}
/* z = sqrt(x) % p = x^((p+1)/4) % p */
/* e = (p+1)/4 */
e := new(big.Int).Add(p, big.NewInt(1))
e = e.Rsh(e, 2)
z = expMod(x, e, p)
return z
}
/*** Point Arithmetic on Curve ***/
// IsInfinity checks if point P is infinity on EllipticCurve ec.
func (ec *Curve) IsInfinity(P Point) bool {
/* We use (nil,nil) to represent O, the point at infinity. */
if P.X == nil && P.Y == nil {
return true
}
return false
}
// IsOnCurve checks if point P is on EllipticCurve ec.
func (ec Curve) IsOnCurve(P1, P2 *big.Int) bool {
P := Point{P1, P2}
if ec.IsInfinity(P) {
return false
}
/* y**2 = x**3 + a*x + b % p */
lhs := mulMod(P.Y, P.Y, ec.P)
rhs := addMod(
addMod(
expMod(P.X, big.NewInt(3), ec.P),
mulMod(ec.A, P.X, ec.P), ec.P),
ec.B, ec.P)
if lhs.Cmp(rhs) == 0 {
return true
}
return false
}
// Add computes R = P + Q on EllipticCurve ec.
func (ec Curve) Add(P1, P2, Q1, Q2 *big.Int) (R1 *big.Int, R2 *big.Int) {
/* See rules 1-5 on SEC1 pg.7 http://www.secg.org/collateral/sec1_final.pdf */
P := Point{P1, P2}
Q := Point{Q1, Q2}
R := Point{}
if ec.IsInfinity(P) && ec.IsInfinity(Q) {
/* Rule #1 Identity */
/* R = O + O = O */
R.X = nil
R.Y = nil
} else if ec.IsInfinity(P) {
/* Rule #2 Identity */
/* R = O + Q = Q */
R.X = new(big.Int).Set(Q.X)
R.Y = new(big.Int).Set(Q.Y)
} else if ec.IsInfinity(Q) {
/* Rule #2 Identity */
/* R = P + O = P */
R.X = new(big.Int).Set(P.X)
R.Y = new(big.Int).Set(P.Y)
} else if P.X.Cmp(Q.X) == 0 && addMod(P.Y, Q.Y, ec.P).Sign() == 0 {
/* Rule #3 Identity */
/* R = (x,y) + (x,-y) = O */
R.X = nil
R.Y = nil
} else if P.X.Cmp(Q.X) == 0 && P.Y.Cmp(Q.Y) == 0 && P.Y.Sign() != 0 {
/* Rule #5 Point doubling */
/* R = P + P */
/* Lambda = (3*P.X*P.X + a) / (2*P.Y) */
num := addMod(
mulMod(big.NewInt(3),
mulMod(P.X, P.X, ec.P), ec.P),
ec.A, ec.P)
den := invMod(mulMod(big.NewInt(2), P.Y, ec.P), ec.P)
lambda := mulMod(num, den, ec.P)
/* R.X = lambda*lambda - 2*P.X */
R.X = subMod(
mulMod(lambda, lambda, ec.P),
mulMod(big.NewInt(2), P.X, ec.P),
ec.P)
/* R.Y = lambda*(P.X - R.X) - P.Y */
R.Y = subMod(
mulMod(lambda, subMod(P.X, R.X, ec.P), ec.P),
P.Y, ec.P)
} else if P.X.Cmp(Q.X) != 0 {
/* Rule #4 Point addition */
/* R = P + Q */
/* Lambda = (Q.Y - P.Y) / (Q.X - P.X) */
num := subMod(Q.Y, P.Y, ec.P)
den := invMod(subMod(Q.X, P.X, ec.P), ec.P)
lambda := mulMod(num, den, ec.P)
/* R.X = lambda*lambda - P.X - Q.X */
R.X = subMod(
subMod(
mulMod(lambda, lambda, ec.P),
P.X, ec.P),
Q.X, ec.P)
/* R.Y = lambda*(P.X - R.X) - P.Y */
R.Y = subMod(
mulMod(lambda,
subMod(P.X, R.X, ec.P), ec.P),
P.Y, ec.P)
} else {
panic(fmt.Sprintf("Unsupported point addition: %v + %v", P.format(), Q.format()))
}
return R.X, R.Y
}
// ScalarMult computes Q = k * P on EllipticCurve ec.
func (ec Curve) ScalarMult(P1, P2 *big.Int, l []byte) (Q1, Q2 *big.Int) {
/* Note: this function is not constant time, due to the branching nature of
* the underlying point Add() function. */
/* Montgomery Ladder Point Multiplication
*
* Implementation based on pseudocode here:
* See https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder */
P := Point{P1, P2}
k := big.Int{}
k.SetBytes(l)
var R0 Point
var R1 Point
R0.X = nil
R0.Y = nil
R1.X = new(big.Int).Set(P.X)
R1.Y = new(big.Int).Set(P.Y)
for i := ec.N.BitLen() - 1; i >= 0; i-- {
if k.Bit(i) == 0 {
R1.X, R1.Y = ec.Add(R0.X, R0.Y, R1.X, R1.Y)
R0.X, R0.Y = ec.Add(R0.X, R0.Y, R0.X, R0.Y)
} else {
R0.X, R0.Y = ec.Add(R0.X, R0.Y, R1.X, R1.Y)
R1.X, R1.Y = ec.Add(R1.X, R1.Y, R1.X, R1.Y)
}
}
return R0.X, R0.Y
}
// ScalarBaseMult computes Q = k * G on EllipticCurve ec.
func (ec Curve) ScalarBaseMult(k []byte) (Q1, Q2 *big.Int) {
return ec.ScalarMult(ec.G.X, ec.G.Y, k)
}
// Decompress decompresses coordinate x and ylsb (y's least significant bit) into a Point P on EllipticCurve ec.
func (ec *Curve) Decompress(x *big.Int, ylsb uint) (P Point, err error) {
/* y**2 = x**3 + a*x + b % p */
rhs := addMod(
addMod(
expMod(x, big.NewInt(3), ec.P),
mulMod(ec.A, x, ec.P),
ec.P),
ec.B, ec.P)
/* y = sqrt(rhs) % p */
y := sqrtMod(rhs, ec.P)
/* Use -y if opposite lsb is required */
if y.Bit(0) != (ylsb & 0x1) {
y = subMod(big.NewInt(0), y, ec.P)
}
P.X = x
P.Y = y
if !ec.IsOnCurve(P.X, P.Y) {
return P, errors.New("Compressed (x, ylsb) not on curve.")
}
return P, nil
}
func (ec Curve) Double(x1, y1 *big.Int) (x, y *big.Int) {
x = &big.Int{}
x.SetBytes([]byte{0x00})
y = &big.Int{}
y.SetBytes([]byte{0x00})
return x, y
}

View file

@ -0,0 +1,231 @@
/* btckeygenie v1.0.0
* https://github.com/vsergeev/btckeygenie
* License: MIT
*/
package elliptic
import (
"encoding/hex"
"math/big"
"testing"
)
func init() {
curve = NewEllipticCurve(Secp256k1)
}
func hex2int(hexstring string) (v *big.Int) {
v, _ = new(big.Int).SetString(hexstring, 16)
return v
}
func TestOnCurve(t *testing.T) {
if !curve.IsOnCurve(curve.G.X, curve.G.Y) {
t.Fatal("failure G on curve")
}
t.Log("G on curve")
}
func TestInfinity(t *testing.T) {
O := Point{nil, nil}
/* O not on curve */
if curve.IsOnCurve(O.X, O.Y) {
t.Fatal("failure O on curve")
}
/* O is infinity */
if !curve.IsInfinity(O) {
t.Fatal("failure O not infinity on curve")
}
t.Log("O is not on curve and is infinity")
}
func TestPointAdd(t *testing.T) {
X := "50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352"
Y := "2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6"
P := Point{hex2int(X), hex2int(Y)}
O := Point{nil, nil}
/* R = O + O = O */
{
R1, R2 := curve.Add(O.X, O.Y, O.X, O.Y)
R := Point{R1, R2}
if !curve.IsInfinity(R) {
t.Fatal("failure O + O = O")
}
t.Log("success O + O = O")
}
/* R = P + O = P */
{
R1, R2 := curve.Add(P.X, P.Y, O.X, O.Y)
R := Point{R1, R2}
if R.X.Cmp(P.X) != 0 || R.Y.Cmp(P.Y) != 0 {
t.Fatal("failure P + O = P")
}
t.Log("success P + O = P")
}
/* R = O + Q = Q */
{
R1, R2 := curve.Add(O.X, O.Y, P.X, P.Y)
R := Point{R1, R2}
if R.X.Cmp(P.X) != 0 || R.Y.Cmp(P.Y) != 0 {
t.Fatal("failure O + Q = Q")
}
t.Log("success O + Q = Q")
}
/* R = (x,y) + (x,-y) = O */
{
Q := Point{P.X, subMod(big.NewInt(0), P.Y, curve.P)}
R1, R2 := curve.Add(P.X, P.Y, Q.X, Q.Y)
R := Point{R1, R2}
if !curve.IsInfinity(R) {
t.Fatal("failure (x,y) + (x,-y) = O")
}
t.Log("success (x,y) + (x,-y) = O")
}
/* R = P + P */
{
PP := Point{hex2int("5dbcd5dfea550eb4fd3b5333f533f086bb5267c776e2a1a9d8e84c16a6743d82"), hex2int("8dde3986b6cbe395da64b6e95fb81f8af73f6e0cf1100555005bb4ba2a6a4a07")}
R1, R2 := curve.Add(P.X, P.Y, P.X, P.Y)
R := Point{R1, R2}
if R.X.Cmp(PP.X) != 0 || R.Y.Cmp(PP.Y) != 0 {
t.Fatal("failure P + P")
}
t.Log("success P + P")
}
Q := Point{hex2int("a83b8de893467d3a88d959c0eb4032d9ce3bf80f175d4d9e75892a3ebb8ab7e5"), hex2int("370f723328c24b7a97fe34063ba68f253fb08f8645d7c8b9a4ff98e3c29e7f0d")}
PQ := Point{hex2int("fe7d540002e4355eb0ec36c217b4735495de7bd8634055ded3683b0e9da70ef1"), hex2int("fc033c1d74cb34e087a3495e505c0fc0e9e3e8297994878d89d882254ce8a9ef")}
/* R = P + Q */
{
R1, R2 := curve.Add(P.X, P.Y, Q.X, Q.Y)
R := Point{R1, R2}
if R.X.Cmp(PQ.X) != 0 || R.Y.Cmp(PQ.Y) != 0 {
t.Fatal("failure P + Q")
}
t.Log("success P + Q")
}
/* R = Q + P */
{
R1, R2 := curve.Add(Q.X, Q.Y, P.X, P.Y)
R := Point{R1, R2}
if R.X.Cmp(PQ.X) != 0 || R.Y.Cmp(PQ.Y) != 0 {
t.Fatal("failure Q + P")
}
t.Log("success Q + P")
}
}
func TestPointScalarMult(t *testing.T) {
X := "50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352"
Y := "2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6"
P := Point{hex2int(X), hex2int(Y)}
/* Q = k*P */
{
T := Point{hex2int("87d592bfdd24adb52147fea343db93e10d0585bc66d91e365c359973c0dc7067"), hex2int("a374e206cb7c8cd1074bdf9bf6ddea135f983aaa6475c9ab3bb4c38a0046541b")}
input, _ := hex.DecodeString("14eb373700c3836404acd0820d9fa8dfa098d26177ca6e18b1c7f70c6af8fc18")
Q1, Q2 := curve.ScalarMult(P.X, P.Y, input)
Q := Point{Q1, Q2}
if Q.X.Cmp(T.X) != 0 || Q.Y.Cmp(T.Y) != 0 {
t.Fatal("failure k*P")
}
t.Log("success k*P")
}
/* Q = n*G = O */
{
Q1, Q2 := curve.ScalarMult(curve.G.X, curve.G.Y, curve.N.Bytes())
Q := Point{Q1, Q2}
if !curve.IsInfinity(Q) {
t.Fatal("failure n*G = O")
}
t.Log("success n*G = O")
}
}
func TestPointScalarBaseMult(t *testing.T) {
/* Sample Private Key */
D := "18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725"
/* Sample Corresponding Public Key */
X := "50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352"
Y := "2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6"
P := Point{hex2int(X), hex2int(Y)}
/* Q = d*G = P */
Q1, Q2 := curve.ScalarBaseMult(hex2int(D).Bytes())
Q := Point{Q1, Q2}
if P.X.Cmp(Q.X) != 0 || P.Y.Cmp(Q.Y) != 0 {
t.Fatal("failure Q = d*G")
}
t.Log("success Q = d*G")
/* Q on curve */
if !curve.IsOnCurve(Q.X, Q.Y) {
t.Fatal("failure Q on curve")
}
t.Log("success Q on curve")
/* R = 0*G = O */
R1, R2 := curve.ScalarBaseMult(big.NewInt(0).Bytes())
R := Point{R1, R2}
if !curve.IsInfinity(R) {
t.Fatal("failure 0*G = O")
}
t.Log("success 0*G = O")
}
func TestPointDecompress(t *testing.T) {
/* Valid points */
var validDecompressVectors = []Point{
{hex2int("50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352"), hex2int("2cd470243453a299fa9e77237716103abc11a1df38855ed6f2ee187e9c582ba6")},
{hex2int("a83b8de893467d3a88d959c0eb4032d9ce3bf80f175d4d9e75892a3ebb8ab7e5"), hex2int("370f723328c24b7a97fe34063ba68f253fb08f8645d7c8b9a4ff98e3c29e7f0d")},
{hex2int("f680556678e25084a82fa39e1b1dfd0944f7e69fddaa4e03ce934bd6b291dca0"), hex2int("52c10b721d34447e173721fb0151c68de1106badb089fb661523b8302a9097f5")},
{hex2int("241febb8e23cbd77d664a18f66ad6240aaec6ecdc813b088d5b901b2e285131f"), hex2int("513378d9ff94f8d3d6c420bd13981df8cd50fd0fbd0cb5afabb3e66f2750026d")},
}
for i := 0; i < len(validDecompressVectors); i++ {
P, err := curve.Decompress(validDecompressVectors[i].X, validDecompressVectors[i].Y.Bit(0))
if err != nil {
t.Fatalf("failure decompress P, got error %v on index %d", err, i)
}
if P.X.Cmp(validDecompressVectors[i].X) != 0 || P.Y.Cmp(validDecompressVectors[i].Y) != 0 {
t.Fatalf("failure decompress P, got mismatch on index %d", i)
}
}
t.Log("success Decompress() on valid vectors")
/* Invalid points */
var invalidDecompressVectors = []struct {
X *big.Int
YLsb uint
}{
{hex2int("c8e337cee51ae9af3c0ef923705a0cb1b76f7e8463b3d3060a1c8d795f9630fd"), 0},
{hex2int("c8e337cee51ae9af3c0ef923705a0cb1b76f7e8463b3d3060a1c8d795f9630fd"), 1},
}
for i := 0; i < len(invalidDecompressVectors); i++ {
_, err := curve.Decompress(invalidDecompressVectors[i].X, invalidDecompressVectors[i].YLsb)
if err == nil {
t.Fatalf("failure decompress invalid P, got decompressed point on index %d", i)
}
}
t.Log("success Decompress() on invalid vectors")
}

77
pkg/crypto/hash/hash.go Executable file
View file

@ -0,0 +1,77 @@
package hash
import (
"crypto/sha256"
"io"
"github.com/CityOfZion/neo-go/pkg/wire/util"
"golang.org/x/crypto/ripemd160"
)
func Sha256(data []byte) (util.Uint256, error) {
var hash util.Uint256
hasher := sha256.New()
hasher.Reset()
_, err := hasher.Write(data)
hash, err = util.Uint256DecodeBytes(hasher.Sum(nil))
if err != nil {
return hash, err
}
return hash, nil
}
func DoubleSha256(data []byte) (util.Uint256, error) {
var hash util.Uint256
h1, err := Sha256(data)
if err != nil {
return hash, err
}
hash, err = Sha256(h1.Bytes())
if err != nil {
return hash, err
}
return hash, nil
}
func RipeMD160(data []byte) (util.Uint160, error) {
var hash util.Uint160
hasher := ripemd160.New()
hasher.Reset()
_, err := io.WriteString(hasher, string(data))
hash, err = util.Uint160DecodeBytes(hasher.Sum(nil))
if err != nil {
return hash, err
}
return hash, nil
}
func Hash160(data []byte) (util.Uint160, error) {
var hash util.Uint160
h1, err := Sha256(data)
h2, err := RipeMD160(h1.Bytes())
hash, err = util.Uint160DecodeBytes(h2.Bytes())
if err != nil {
return hash, err
}
return hash, nil
}
func Checksum(data []byte) ([]byte, error) {
hash, err := Sum(data)
if err != nil {
return nil, err
}
return hash[:4], nil
}
func Sum(b []byte) (util.Uint256, error) {
hash, err := DoubleSha256((b))
return hash, err
}

62
pkg/crypto/hash/hash_test.go Executable file
View file

@ -0,0 +1,62 @@
package hash
import (
"encoding/hex"
"testing"
"github.com/stretchr/testify/assert"
)
func TestSha256(t *testing.T) {
input := []byte("hello")
data, err := Sha256(input)
if err != nil {
t.Fatal(err)
}
expected := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
actual := hex.EncodeToString(data.Bytes()) // MARK: In the DecodeBytes function, there is a bytes reverse, not sure why?
assert.Equal(t, expected, actual)
}
func TestHashDoubleSha256(t *testing.T) {
input := []byte("hello")
data, err := DoubleSha256(input)
if err != nil {
t.Fatal(err)
}
firstSha, _ := Sha256(input)
doubleSha, _ := Sha256(firstSha.Bytes())
expected := hex.EncodeToString(doubleSha.Bytes())
actual := hex.EncodeToString(data.Bytes())
assert.Equal(t, expected, actual)
}
func TestHashRipeMD160(t *testing.T) {
input := []byte("hello")
data, err := RipeMD160(input)
if err != nil {
t.Fatal(err)
}
expected := "108f07b8382412612c048d07d13f814118445acd"
actual := hex.EncodeToString(data.Bytes())
assert.Equal(t, expected, actual)
}
func TestHash160(t *testing.T) {
input := "02cccafb41b220cab63fd77108d2d1ebcffa32be26da29a04dca4996afce5f75db"
publicKeyBytes, _ := hex.DecodeString(input)
data, err := Hash160(publicKeyBytes)
if err != nil {
t.Fatal(err)
}
expected := "c8e2b685cc70ec96743b55beb9449782f8f775d8"
actual := hex.EncodeToString(data.Bytes())
assert.Equal(t, expected, actual)
}

View file

@ -0,0 +1,117 @@
package privatekey
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"io"
"math/big"
"github.com/CityOfZion/neo-go/pkg/crypto/publickey"
"github.com/CityOfZion/neo-go/pkg/crypto/base58"
"github.com/CityOfZion/neo-go/pkg/crypto/elliptic"
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/crypto/rfc6979"
)
// PrivateKey represents a NEO private key.
type PrivateKey struct {
b []byte
}
func NewPrivateKey() (*PrivateKey, error) {
curve := elliptic.NewEllipticCurve(elliptic.Secp256r1)
b := make([]byte, curve.N.BitLen()/8+8)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
return nil, err
}
d := new(big.Int).SetBytes(b)
d.Mod(d, new(big.Int).Sub(curve.N, big.NewInt(1)))
d.Add(d, big.NewInt(1))
p := &PrivateKey{b: d.Bytes()}
return p, nil
}
func NewPrivateKeyFromHex(str string) (*PrivateKey, error) {
b, err := hex.DecodeString(str)
if err != nil {
return nil, err
}
return NewPrivateKeyFromBytes(b)
}
// NewPrivateKeyFromBytes returns a NEO PrivateKey from the given byte slice.
func NewPrivateKeyFromBytes(b []byte) (*PrivateKey, error) {
if len(b) != 32 {
return nil, fmt.Errorf(
"invalid byte length: expected %d bytes got %d", 32, len(b),
)
}
return &PrivateKey{b}, nil
}
func (p *PrivateKey) PublicKey() (*publickey.PublicKey, error) {
var (
c = elliptic.NewEllipticCurve(elliptic.Secp256r1)
q = new(big.Int).SetBytes(p.b)
)
p1, p2 := c.ScalarBaseMult(q.Bytes())
point := elliptic.Point{
X: p1,
Y: p2,
}
if !c.IsOnCurve(p1, p2) {
return nil, errors.New("failed to derive public key using elliptic curve")
}
return &publickey.PublicKey{
Curve: c,
Point: point,
}, nil
}
func WIFEncode(key []byte) (s string) {
if len(key) != 32 {
return "invalid private key length"
}
buf := new(bytes.Buffer)
buf.WriteByte(0x80)
buf.Write(key)
buf.WriteByte(0x01)
checksum, _ := hash.Checksum(buf.Bytes())
buf.Write(checksum)
WIF := base58.Encode(buf.Bytes())
return WIF
}
func (p *PrivateKey) Sign(data []byte) ([]byte, error) {
curve := elliptic.NewEllipticCurve(elliptic.Secp256r1)
key := p.b
digest, _ := hash.Sha256(data)
r, s, err := rfc6979.SignECDSA(curve, key, digest[:], sha256.New)
if err != nil {
return nil, err
}
curveOrderByteSize := curve.P.BitLen() / 8
rBytes, sBytes := r.Bytes(), s.Bytes()
signature := make([]byte, curveOrderByteSize*2)
copy(signature[curveOrderByteSize-len(rBytes):], rBytes)
copy(signature[curveOrderByteSize*2-len(sBytes):], sBytes)
return signature, nil
}

View file

@ -0,0 +1,48 @@
package privatekey
import (
"encoding/hex"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestPrivateKeyToPublicKey(t *testing.T) {
input := "495d528227c7dcc234c690af1222e67cde916dac1652cad97e0263825a8268a6"
privateKey, err := NewPrivateKeyFromHex(input)
if err != nil {
t.Fatal(err)
}
pubKey, _ := privateKey.PublicKey()
pubKeyBytes := pubKey.Bytes()
actual := hex.EncodeToString(pubKeyBytes)
expected := "03cd4c4ee9c8e1fae9d12ecf7c96cb3a057b550393f9e82182c4dae1139871682e"
assert.Equal(t, expected, actual)
}
func TestWIFEncode(t *testing.T) {
input := "29bbf53185a973d2e3803cb92908fd08117486d1f2e7bab73ed0d00255511637"
inputBytes, _ := hex.DecodeString(input)
actual := WIFEncode(inputBytes)
expected := "KxcqV28rGDcpVR3fYg7R9vricLpyZ8oZhopyFLAWuRv7Y8TE9WhW"
assert.Equal(t, expected, actual)
}
func TestSigning(t *testing.T) {
// These were taken from the rfcPage:https://tools.ietf.org/html/rfc6979#page-33
// public key: U = xG
//Ux = 60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6
//Uy = 7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299
PrivateKey, _ := NewPrivateKeyFromHex("C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721")
data, err := PrivateKey.Sign([]byte("sample"))
if err != nil {
t.Fatal(err)
}
r := "EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716"
s := "F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8"
assert.Equal(t, strings.ToLower(r+s), hex.EncodeToString(data))
}

View file

@ -0,0 +1,20 @@
package pubkeytesthelper
import (
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/crypto/privatekey"
)
func SignDataWithRandomPrivateKey(data []byte) (bool, error) {
hashedData, _ := hash.Sha256(data)
privKey, _ := privatekey.NewPrivateKey()
signedData, err := privKey.Sign(data)
pubKey, _ := privKey.PublicKey()
result := pubKey.Verify(signedData, hashedData.Bytes())
if err != nil {
return false, err
}
return result, nil
}

View file

@ -0,0 +1,34 @@
package pubkeytesthelper
import (
"testing"
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/crypto/privatekey"
"github.com/stretchr/testify/assert"
)
func TestPubKeyVerify(t *testing.T) {
actual, err := SignDataWithRandomPrivateKey([]byte("sample"))
if err != nil {
t.Fatal(err)
}
expected := true
assert.Equal(t, expected, actual)
}
func TestWrongPubKey(t *testing.T) {
privKey, _ := privatekey.NewPrivateKey()
sample := []byte("sample")
hashedData, _ := hash.Sha256(sample)
signedData, _ := privKey.Sign(sample)
secondPrivKey, _ := privatekey.NewPrivateKey()
wrongPubKey, _ := secondPrivKey.PublicKey()
actual := wrongPubKey.Verify(signedData, hashedData.Bytes())
expcted := false
assert.Equal(t, expcted, actual)
}

160
pkg/crypto/publickey/publickey.go Executable file
View file

@ -0,0 +1,160 @@
package publickey
import (
"bytes"
"crypto/ecdsa"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"math/big"
"github.com/CityOfZion/neo-go/pkg/crypto/base58"
"github.com/CityOfZion/neo-go/pkg/crypto/elliptic"
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
)
// PublicKeys is a list of public keys.
type PublicKeys []*PublicKey
func (keys PublicKeys) Len() int { return len(keys) }
func (keys PublicKeys) Swap(i, j int) { keys[i], keys[j] = keys[j], keys[i] }
func (keys PublicKeys) Less(i, j int) bool {
if keys[i].X.Cmp(keys[j].X) == -1 {
return true
}
if keys[i].X.Cmp(keys[j].X) == 1 {
return false
}
if keys[i].X.Cmp(keys[j].X) == 0 {
return false
}
return keys[i].Y.Cmp(keys[j].Y) == -1
}
// PublicKey represents a public key and provides a high level
// API around the ECPoint.
type PublicKey struct {
Curve elliptic.Curve
elliptic.Point
}
// NewPublicKeyFromString return a public key created from the
// given hex string.
func NewPublicKeyFromString(s string) (*PublicKey, error) {
b, err := hex.DecodeString(s)
if err != nil {
return nil, err
}
curve := elliptic.NewEllipticCurve(elliptic.Secp256r1)
pubKey := &PublicKey{curve, elliptic.Point{}}
if err := pubKey.DecodeBinary(bytes.NewReader(b)); err != nil {
return nil, err
}
return pubKey, nil
}
// Bytes returns the byte array representation of the public key.
func (p *PublicKey) Bytes() []byte {
if p.Curve.IsInfinity(p.Point) {
return []byte{0x00}
}
var (
x = p.X.Bytes()
paddedX = append(bytes.Repeat([]byte{0x00}, 32-len(x)), x...)
prefix = byte(0x03)
)
if p.Y.Bit(0) == 0 {
prefix = byte(0x02)
}
return append([]byte{prefix}, paddedX...)
}
func (p *PublicKey) ToAddress() string {
publicKeyBytes := p.Bytes()
publicKeyBytes = append([]byte{0x21}, publicKeyBytes...) // 0x21 = length of pubKey
publicKeyBytes = append(publicKeyBytes, 0xAC) // 0xAC = CheckSig
hash160PubKey, _ := hash.Hash160(publicKeyBytes)
versionHash160PubKey := append([]byte{0x17}, hash160PubKey.Bytes()...)
checksum, _ := hash.Checksum(versionHash160PubKey)
checkVersionHash160 := append(versionHash160PubKey, checksum...)
address := base58.Encode(checkVersionHash160)
return address
}
// DecodeBinary decodes a PublicKey from the given io.Reader.
func (p *PublicKey) DecodeBinary(r io.Reader) error {
var prefix uint8
if err := binary.Read(r, binary.LittleEndian, &prefix); err != nil {
return err
}
// Infinity
if prefix == 0x00 {
p.Point = elliptic.Point{}
return nil
}
// Compressed public keys.
if prefix == 0x02 || prefix == 0x03 {
b := make([]byte, 32)
if err := binary.Read(r, binary.LittleEndian, b); err != nil {
return err
}
var err error
p.Point, err = p.Curve.Decompress(new(big.Int).SetBytes(b), uint(prefix&0x1))
if err != nil {
return err
}
} else if prefix == 0x04 {
buf := make([]byte, 65)
if err := binary.Read(r, binary.LittleEndian, buf); err != nil {
return err
}
p.X = new(big.Int).SetBytes(buf[1:33])
p.Y = new(big.Int).SetBytes(buf[33:65])
} else {
return fmt.Errorf("invalid prefix %d", prefix)
}
return nil
}
// EncodeBinary encodes a PublicKey to the given io.Writer.
func (p *PublicKey) EncodeBinary(w io.Writer) error {
return binary.Write(w, binary.LittleEndian, p.Bytes())
}
func (p *PublicKey) Verify(signature []byte, hash []byte) bool {
publicKey := &ecdsa.PublicKey{}
publicKey.Curve = p.Curve
publicKey.X = p.X
publicKey.Y = p.Y
if p.X == nil || p.Y == nil {
return false
}
rBytes := new(big.Int).SetBytes(signature[0:32])
sBytes := new(big.Int).SetBytes(signature[32:64])
return ecdsa.Verify(publicKey, hash, rBytes, sBytes)
}

View file

@ -0,0 +1,81 @@
package publickey
import (
"bytes"
"crypto/rand"
"encoding/hex"
"io"
"math/big"
"testing"
"github.com/CityOfZion/neo-go/pkg/crypto/elliptic"
"github.com/stretchr/testify/assert"
)
func TestDecodeFromString(t *testing.T) {
str := "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"
pubKey, err := NewPublicKeyFromString(str)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, str, hex.EncodeToString(pubKey.Bytes()))
}
func TestEncodeDecodeInfinity(t *testing.T) {
curve := elliptic.NewEllipticCurve(elliptic.Secp256r1)
key := &PublicKey{curve, elliptic.Point{}}
buf := new(bytes.Buffer)
assert.Nil(t, key.EncodeBinary(buf))
assert.Equal(t, 1, buf.Len())
keyDecode := &PublicKey{}
assert.Nil(t, keyDecode.DecodeBinary(buf))
assert.Equal(t, []byte{0x00}, keyDecode.Bytes())
}
func TestEncodeDecodePublicKey(t *testing.T) {
curve := elliptic.NewEllipticCurve(elliptic.Secp256r1)
for i := 0; i < 4; i++ {
p := &PublicKey{curve, randomECPoint()}
buf := new(bytes.Buffer)
assert.Nil(t, p.EncodeBinary(buf))
pDecode := &PublicKey{curve, elliptic.Point{}}
assert.Nil(t, pDecode.DecodeBinary(buf))
assert.Equal(t, p.X, pDecode.X)
}
}
func TestPubkeyToAddress(t *testing.T) {
pubKey, err := NewPublicKeyFromString("031ee4e73a17d8f76dc02532e2620bcb12425b33c0c9f9694cc2caa8226b68cad4")
if err != nil {
t.Fatal(err)
}
actual := pubKey.ToAddress()
expected := "AUpGsNCHzSimeMRVPQfhwrVdiUp8Q2N2Qx"
assert.Equal(t, expected, actual)
}
func randomECPoint() elliptic.Point {
curve := elliptic.NewEllipticCurve(elliptic.Secp256r1)
b := make([]byte, curve.N.BitLen()/8+8)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
return elliptic.Point{}
}
d := new(big.Int).SetBytes(b)
d.Mod(d, new(big.Int).Sub(curve.N, big.NewInt(1)))
d.Add(d, big.NewInt(1))
q := new(big.Int).SetBytes(d.Bytes())
P1, P2 := curve.ScalarBaseMult(q.Bytes())
return elliptic.Point{
X: P1,
Y: P2,
}
}

21
pkg/crypto/rfc6979/LICENSE Executable file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Coda Hale
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.

45
pkg/crypto/rfc6979/dsa.go Executable file
View file

@ -0,0 +1,45 @@
package rfc6979
import (
"crypto/dsa"
"hash"
"math/big"
)
// SignDSA signs an arbitrary length hash (which should be the result of hashing
// a larger message) using the private key, priv. It returns the signature as a
// pair of integers.
//
// Note that FIPS 186-3 section 4.6 specifies that the hash should be truncated
// to the byte-length of the subgroup. This function does not perform that
// truncation itself.
func SignDSA(priv *dsa.PrivateKey, hash []byte, alg func() hash.Hash) (r, s *big.Int, err error) {
n := priv.Q.BitLen()
if n&7 != 0 {
err = dsa.ErrInvalidPublicKey
return
}
n >>= 3
generateSecret(priv.Q, priv.X, alg, hash, func(k *big.Int) bool {
inv := new(big.Int).ModInverse(k, priv.Q)
r = new(big.Int).Exp(priv.G, k, priv.P)
r.Mod(r, priv.Q)
if r.Sign() == 0 {
return false
}
z := new(big.Int).SetBytes(hash)
s = new(big.Int).Mul(priv.X, r)
s.Add(s, z)
s.Mod(s, priv.Q)
s.Mul(s, inv)
s.Mod(s, priv.Q)
return s.Sign() != 0
})
return
}

270
pkg/crypto/rfc6979/dsa_test.go Executable file
View file

@ -0,0 +1,270 @@
package rfc6979_test
import (
"crypto/dsa"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"hash"
"math/big"
"testing"
"github.com/o3labs/neo-utils/neoutils/rfc6979"
)
type dsaFixture struct {
name string
key *dsaKey
alg func() hash.Hash
message string
r, s string
}
type dsaKey struct {
key *dsa.PrivateKey
subgroup int
}
var dsa1024 = &dsaKey{
key: &dsa.PrivateKey{
PublicKey: dsa.PublicKey{
Parameters: dsa.Parameters{
P: dsaLoadInt("86F5CA03DCFEB225063FF830A0C769B9DD9D6153AD91D7CE27F787C43278B447E6533B86B18BED6E8A48B784A14C252C5BE0DBF60B86D6385BD2F12FB763ED8873ABFD3F5BA2E0A8C0A59082EAC056935E529DAF7C610467899C77ADEDFC846C881870B7B19B2B58F9BE0521A17002E3BDD6B86685EE90B3D9A1B02B782B1779"),
Q: dsaLoadInt("996F967F6C8E388D9E28D01E205FBA957A5698B1"),
G: dsaLoadInt("07B0F92546150B62514BB771E2A0C0CE387F03BDA6C56B505209FF25FD3C133D89BBCD97E904E09114D9A7DEFDEADFC9078EA544D2E401AEECC40BB9FBBF78FD87995A10A1C27CB7789B594BA7EFB5C4326A9FE59A070E136DB77175464ADCA417BE5DCE2F40D10A46A3A3943F26AB7FD9C0398FF8C76EE0A56826A8A88F1DBD"),
},
Y: dsaLoadInt("5DF5E01DED31D0297E274E1691C192FE5868FEF9E19A84776454B100CF16F65392195A38B90523E2542EE61871C0440CB87C322FC4B4D2EC5E1E7EC766E1BE8D4CE935437DC11C3C8FD426338933EBFE739CB3465F4D3668C5E473508253B1E682F65CBDC4FAE93C2EA212390E54905A86E2223170B44EAA7DA5DD9FFCFB7F3B"),
},
X: dsaLoadInt("411602CB19A6CCC34494D79D98EF1E7ED5AF25F7"),
},
subgroup: 160,
}
var dsa2048 = &dsaKey{
key: &dsa.PrivateKey{
PublicKey: dsa.PublicKey{
Parameters: dsa.Parameters{
P: dsaLoadInt("9DB6FB5951B66BB6FE1E140F1D2CE5502374161FD6538DF1648218642F0B5C48C8F7A41AADFA187324B87674FA1822B00F1ECF8136943D7C55757264E5A1A44FFE012E9936E00C1D3E9310B01C7D179805D3058B2A9F4BB6F9716BFE6117C6B5B3CC4D9BE341104AD4A80AD6C94E005F4B993E14F091EB51743BF33050C38DE235567E1B34C3D6A5C0CEAA1A0F368213C3D19843D0B4B09DCB9FC72D39C8DE41F1BF14D4BB4563CA28371621CAD3324B6A2D392145BEBFAC748805236F5CA2FE92B871CD8F9C36D3292B5509CA8CAA77A2ADFC7BFD77DDA6F71125A7456FEA153E433256A2261C6A06ED3693797E7995FAD5AABBCFBE3EDA2741E375404AE25B"),
Q: dsaLoadInt("F2C3119374CE76C9356990B465374A17F23F9ED35089BD969F61C6DDE9998C1F"),
G: dsaLoadInt("5C7FF6B06F8F143FE8288433493E4769C4D988ACE5BE25A0E24809670716C613D7B0CEE6932F8FAA7C44D2CB24523DA53FBE4F6EC3595892D1AA58C4328A06C46A15662E7EAA703A1DECF8BBB2D05DBE2EB956C142A338661D10461C0D135472085057F3494309FFA73C611F78B32ADBB5740C361C9F35BE90997DB2014E2EF5AA61782F52ABEB8BD6432C4DD097BC5423B285DAFB60DC364E8161F4A2A35ACA3A10B1C4D203CC76A470A33AFDCBDD92959859ABD8B56E1725252D78EAC66E71BA9AE3F1DD2487199874393CD4D832186800654760E1E34C09E4D155179F9EC0DC4473F996BDCE6EED1CABED8B6F116F7AD9CF505DF0F998E34AB27514B0FFE7"),
},
Y: dsaLoadInt("667098C654426C78D7F8201EAC6C203EF030D43605032C2F1FA937E5237DBD949F34A0A2564FE126DC8B715C5141802CE0979C8246463C40E6B6BDAA2513FA611728716C2E4FD53BC95B89E69949D96512E873B9C8F8DFD499CC312882561ADECB31F658E934C0C197F2C4D96B05CBAD67381E7B768891E4DA3843D24D94CDFB5126E9B8BF21E8358EE0E0A30EF13FD6A664C0DCE3731F7FB49A4845A4FD8254687972A2D382599C9BAC4E0ED7998193078913032558134976410B89D2C171D123AC35FD977219597AA7D15C1A9A428E59194F75C721EBCBCFAE44696A499AFA74E04299F132026601638CB87AB79190D4A0986315DA8EEC6561C938996BEADF"),
},
X: dsaLoadInt("69C7548C21D0DFEA6B9A51C9EAD4E27C33D3B3F180316E5BCAB92C933F0E4DBC"),
},
subgroup: 256,
}
var dsaFixtures = []dsaFixture{
// DSA, 1024 Bits
// https://tools.ietf.org/html/rfc6979#appendix-A.2.1
dsaFixture{
name: "1024/SHA-1 #1",
key: dsa1024,
alg: sha1.New,
message: "sample",
r: "2E1A0C2562B2912CAAF89186FB0F42001585DA55",
s: "29EFB6B0AFF2D7A68EB70CA313022253B9A88DF5",
},
dsaFixture{
name: "1024/SHA-224 #1",
key: dsa1024,
alg: sha256.New224,
message: "sample",
r: "4BC3B686AEA70145856814A6F1BB53346F02101E",
s: "410697B92295D994D21EDD2F4ADA85566F6F94C1",
},
dsaFixture{
name: "1024/SHA-256 #1",
key: dsa1024,
alg: sha256.New,
message: "sample",
r: "81F2F5850BE5BC123C43F71A3033E9384611C545",
s: "4CDD914B65EB6C66A8AAAD27299BEE6B035F5E89",
},
dsaFixture{
name: "1024/SHA-384 #1",
key: dsa1024,
alg: sha512.New384,
message: "sample",
r: "07F2108557EE0E3921BC1774F1CA9B410B4CE65A",
s: "54DF70456C86FAC10FAB47C1949AB83F2C6F7595",
},
dsaFixture{
name: "1024/SHA-512 #1",
key: dsa1024,
alg: sha512.New,
message: "sample",
r: "16C3491F9B8C3FBBDD5E7A7B667057F0D8EE8E1B",
s: "02C36A127A7B89EDBB72E4FFBC71DABC7D4FC69C",
},
dsaFixture{
name: "1024/SHA-1 #2",
key: dsa1024,
alg: sha1.New,
message: "test",
r: "42AB2052FD43E123F0607F115052A67DCD9C5C77",
s: "183916B0230D45B9931491D4C6B0BD2FB4AAF088",
},
dsaFixture{
name: "1024/SHA-224 #2",
key: dsa1024,
alg: sha256.New224,
message: "test",
r: "6868E9964E36C1689F6037F91F28D5F2C30610F2",
s: "49CEC3ACDC83018C5BD2674ECAAD35B8CD22940F",
},
dsaFixture{
name: "1024/SHA-256 #2",
key: dsa1024,
alg: sha256.New,
message: "test",
r: "22518C127299B0F6FDC9872B282B9E70D0790812",
s: "6837EC18F150D55DE95B5E29BE7AF5D01E4FE160",
},
dsaFixture{
name: "1024/SHA-384 #2",
key: dsa1024,
alg: sha512.New384,
message: "test",
r: "854CF929B58D73C3CBFDC421E8D5430CD6DB5E66",
s: "91D0E0F53E22F898D158380676A871A157CDA622",
},
dsaFixture{
name: "1024/SHA-512 #2",
key: dsa1024,
alg: sha512.New,
message: "test",
r: "8EA47E475BA8AC6F2D821DA3BD212D11A3DEB9A0",
s: "7C670C7AD72B6C050C109E1790008097125433E8",
},
// DSA, 2048 Bits
// https://tools.ietf.org/html/rfc6979#appendix-A.2.2
dsaFixture{
name: "2048/SHA-1 #1",
key: dsa2048,
alg: sha1.New,
message: "sample",
r: "3A1B2DBD7489D6ED7E608FD036C83AF396E290DBD602408E8677DAABD6E7445A",
s: "D26FCBA19FA3E3058FFC02CA1596CDBB6E0D20CB37B06054F7E36DED0CDBBCCF",
},
dsaFixture{
name: "2048/SHA-224 #1",
key: dsa2048,
alg: sha256.New224,
message: "sample",
r: "DC9F4DEADA8D8FF588E98FED0AB690FFCE858DC8C79376450EB6B76C24537E2C",
s: "A65A9C3BC7BABE286B195D5DA68616DA8D47FA0097F36DD19F517327DC848CEC",
},
dsaFixture{
name: "2048/SHA-256 #1",
key: dsa2048,
alg: sha256.New,
message: "sample",
r: "EACE8BDBBE353C432A795D9EC556C6D021F7A03F42C36E9BC87E4AC7932CC809",
s: "7081E175455F9247B812B74583E9E94F9EA79BD640DC962533B0680793A38D53",
},
dsaFixture{
name: "2048/SHA-384 #1",
key: dsa2048,
alg: sha512.New384,
message: "sample",
r: "B2DA945E91858834FD9BF616EBAC151EDBC4B45D27D0DD4A7F6A22739F45C00B",
s: "19048B63D9FD6BCA1D9BAE3664E1BCB97F7276C306130969F63F38FA8319021B",
},
dsaFixture{
name: "2048/SHA-512 #1",
key: dsa2048,
alg: sha512.New,
message: "sample",
r: "2016ED092DC5FB669B8EFB3D1F31A91EECB199879BE0CF78F02BA062CB4C942E",
s: "D0C76F84B5F091E141572A639A4FB8C230807EEA7D55C8A154A224400AFF2351",
},
dsaFixture{
name: "2048/SHA-1 #2",
key: dsa2048,
alg: sha1.New,
message: "test",
r: "C18270A93CFC6063F57A4DFA86024F700D980E4CF4E2CB65A504397273D98EA0",
s: "414F22E5F31A8B6D33295C7539C1C1BA3A6160D7D68D50AC0D3A5BEAC2884FAA",
},
dsaFixture{
name: "2048/SHA-224 #2",
key: dsa2048,
alg: sha256.New224,
message: "test",
r: "272ABA31572F6CC55E30BF616B7A265312018DD325BE031BE0CC82AA17870EA3",
s: "E9CC286A52CCE201586722D36D1E917EB96A4EBDB47932F9576AC645B3A60806",
},
dsaFixture{
name: "2048/SHA-256 #2",
key: dsa2048,
alg: sha256.New,
message: "test",
r: "8190012A1969F9957D56FCCAAD223186F423398D58EF5B3CEFD5A4146A4476F0",
s: "7452A53F7075D417B4B013B278D1BB8BBD21863F5E7B1CEE679CF2188E1AB19E",
},
dsaFixture{
name: "2048/SHA-384 #2",
key: dsa2048,
alg: sha512.New384,
message: "test",
r: "239E66DDBE8F8C230A3D071D601B6FFBDFB5901F94D444C6AF56F732BEB954BE",
s: "6BD737513D5E72FE85D1C750E0F73921FE299B945AAD1C802F15C26A43D34961",
},
dsaFixture{
name: "2048/SHA-512 #2",
key: dsa2048,
alg: sha512.New,
message: "test",
r: "89EC4BB1400ECCFF8E7D9AA515CD1DE7803F2DAFF09693EE7FD1353E90A68307",
s: "C9F0BDABCC0D880BB137A994CC7F3980CE91CC10FAF529FC46565B15CEA854E1",
},
}
func TestDSASignatures(t *testing.T) {
for _, f := range dsaFixtures {
testDsaFixture(&f, t)
}
}
func testDsaFixture(f *dsaFixture, t *testing.T) {
t.Logf("Testing %s", f.name)
h := f.alg()
h.Write([]byte(f.message))
digest := h.Sum(nil)
g := f.key.subgroup / 8
if len(digest) > g {
digest = digest[0:g]
}
r, s, err := rfc6979.SignDSA(f.key.key, digest, f.alg)
if err != nil {
t.Error(err)
return
}
expectedR := dsaLoadInt(f.r)
expectedS := dsaLoadInt(f.s)
if r.Cmp(expectedR) != 0 {
t.Errorf("%s: Expected R of %X, got %X", f.name, expectedR, r)
}
if s.Cmp(expectedS) != 0 {
t.Errorf("%s: Expected S of %X, got %X", f.name, expectedS, s)
}
}
func dsaLoadInt(s string) *big.Int {
b, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return new(big.Int).SetBytes(b)
}

59
pkg/crypto/rfc6979/ecdsa.go Executable file
View file

@ -0,0 +1,59 @@
package rfc6979
import (
"hash"
"math/big"
"github.com/CityOfZion/neo-go/pkg/crypto/elliptic"
)
// SignECDSA signs an arbitrary length hash (which should be the result of
// hashing a larger message) using the private key, priv. It returns the
// signature as a pair of integers.
//
// Note that FIPS 186-3 section 4.6 specifies that the hash should be truncated
// to the byte-length of the subgroup. This function does not perform that
// truncation itself.
func SignECDSA(curve elliptic.Curve, priv []byte, hash []byte, alg func() hash.Hash) (r, s *big.Int, err error) {
c := curve
N := c.N
D := new(big.Int)
D.SetBytes(priv)
generateSecret(N, D, alg, hash, func(k *big.Int) bool {
inv := new(big.Int).ModInverse(k, N)
r, _ = curve.ScalarBaseMult(k.Bytes())
r.Mod(r, N)
if r.Sign() == 0 {
return false
}
e := hashToInt(hash, c)
s = new(big.Int).Mul(D, r)
s.Add(s, e)
s.Mul(s, inv)
s.Mod(s, N)
return s.Sign() != 0
})
return
}
// copied from crypto/ecdsa
func hashToInt(hash []byte, c elliptic.Curve) *big.Int {
orderBits := c.N.BitLen()
orderBytes := (orderBits + 7) / 8
if len(hash) > orderBytes {
hash = hash[:orderBytes]
}
ret := new(big.Int).SetBytes(hash)
excess := len(hash)*8 - orderBits
if excess > 0 {
ret.Rsh(ret, uint(excess))
}
return ret
}

447
pkg/crypto/rfc6979/ecdsa_test.go Executable file
View file

@ -0,0 +1,447 @@
package rfc6979_test
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"hash"
"math/big"
"testing"
"github.com/o3labs/neo-utils/neoutils/rfc6979"
)
type ecdsaFixture struct {
name string
key *ecdsaKey
alg func() hash.Hash
message string
r, s string
}
type ecdsaKey struct {
key *ecdsa.PrivateKey
subgroup int
}
var p224 = &ecdsaKey{
key: &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{
Curve: elliptic.P224(),
X: ecdsaLoadInt("00CF08DA5AD719E42707FA431292DEA11244D64FC51610D94B130D6C"),
Y: ecdsaLoadInt("EEAB6F3DEBE455E3DBF85416F7030CBD94F34F2D6F232C69F3C1385A"),
},
D: ecdsaLoadInt("F220266E1105BFE3083E03EC7A3A654651F45E37167E88600BF257C1"),
},
subgroup: 224,
}
var p256 = &ecdsaKey{
key: &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{
Curve: elliptic.P256(),
X: ecdsaLoadInt("60FED4BA255A9D31C961EB74C6356D68C049B8923B61FA6CE669622E60F29FB6"),
Y: ecdsaLoadInt("7903FE1008B8BC99A41AE9E95628BC64F2F1B20C2D7E9F5177A3C294D4462299"),
},
D: ecdsaLoadInt("C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721"),
},
subgroup: 256,
}
var p384 = &ecdsaKey{
key: &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{
Curve: elliptic.P384(),
X: ecdsaLoadInt("EC3A4E415B4E19A4568618029F427FA5DA9A8BC4AE92E02E06AAE5286B300C64DEF8F0EA9055866064A254515480BC13"),
Y: ecdsaLoadInt("8015D9B72D7D57244EA8EF9AC0C621896708A59367F9DFB9F54CA84B3F1C9DB1288B231C3AE0D4FE7344FD2533264720"),
},
D: ecdsaLoadInt("6B9D3DAD2E1B8C1C05B19875B6659F4DE23C3B667BF297BA9AA47740787137D896D5724E4C70A825F872C9EA60D2EDF5"),
},
subgroup: 384,
}
var p521 = &ecdsaKey{
key: &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{
Curve: elliptic.P521(),
X: ecdsaLoadInt("1894550D0785932E00EAA23B694F213F8C3121F86DC97A04E5A7167DB4E5BCD371123D46E45DB6B5D5370A7F20FB633155D38FFA16D2BD761DCAC474B9A2F5023A4"),
Y: ecdsaLoadInt("0493101C962CD4D2FDDF782285E64584139C2F91B47F87FF82354D6630F746A28A0DB25741B5B34A828008B22ACC23F924FAAFBD4D33F81EA66956DFEAA2BFDFCF5"),
},
D: ecdsaLoadInt("0FAD06DAA62BA3B25D2FB40133DA757205DE67F5BB0018FEE8C86E1B68C7E75CAA896EB32F1F47C70855836A6D16FCC1466F6D8FBEC67DB89EC0C08B0E996B83538"),
},
subgroup: 521,
}
var fixtures = []ecdsaFixture{
// ECDSA, 224 Bits (Prime Field)
// https://tools.ietf.org/html/rfc6979#appendix-A.2.4
ecdsaFixture{
name: "P224/SHA-1 #1",
key: p224,
alg: sha1.New,
message: "sample",
r: "22226F9D40A96E19C4A301CE5B74B115303C0F3A4FD30FC257FB57AC",
s: "66D1CDD83E3AF75605DD6E2FEFF196D30AA7ED7A2EDF7AF475403D69",
},
ecdsaFixture{
name: "P224/SHA-224 #1",
key: p224,
alg: sha256.New224,
message: "sample",
r: "1CDFE6662DDE1E4A1EC4CDEDF6A1F5A2FB7FBD9145C12113E6ABFD3E",
s: "A6694FD7718A21053F225D3F46197CA699D45006C06F871808F43EBC",
},
ecdsaFixture{
name: "P224/SHA-256 #1",
key: p224,
alg: sha256.New,
message: "sample",
r: "61AA3DA010E8E8406C656BC477A7A7189895E7E840CDFE8FF42307BA",
s: "BC814050DAB5D23770879494F9E0A680DC1AF7161991BDE692B10101",
},
ecdsaFixture{
name: "P224/SHA-384 #1",
key: p224,
alg: sha512.New384,
message: "sample",
r: "0B115E5E36F0F9EC81F1325A5952878D745E19D7BB3EABFABA77E953",
s: "830F34CCDFE826CCFDC81EB4129772E20E122348A2BBD889A1B1AF1D",
},
ecdsaFixture{
name: "P224/SHA-512 #1",
key: p224,
alg: sha512.New,
message: "sample",
r: "074BD1D979D5F32BF958DDC61E4FB4872ADCAFEB2256497CDAC30397",
s: "A4CECA196C3D5A1FF31027B33185DC8EE43F288B21AB342E5D8EB084",
},
ecdsaFixture{
name: "P224/SHA-1 #2",
key: p224,
alg: sha1.New,
message: "test",
r: "DEAA646EC2AF2EA8AD53ED66B2E2DDAA49A12EFD8356561451F3E21C",
s: "95987796F6CF2062AB8135271DE56AE55366C045F6D9593F53787BD2",
},
ecdsaFixture{
name: "P224/SHA-224 #2",
key: p224,
alg: sha256.New224,
message: "test",
r: "C441CE8E261DED634E4CF84910E4C5D1D22C5CF3B732BB204DBEF019",
s: "902F42847A63BDC5F6046ADA114953120F99442D76510150F372A3F4",
},
ecdsaFixture{
name: "P224/SHA-256 #2",
key: p224,
alg: sha256.New,
message: "test",
r: "AD04DDE87B84747A243A631EA47A1BA6D1FAA059149AD2440DE6FBA6",
s: "178D49B1AE90E3D8B629BE3DB5683915F4E8C99FDF6E666CF37ADCFD",
},
ecdsaFixture{
name: "P224/SHA-384 #2",
key: p224,
alg: sha512.New384,
message: "test",
r: "389B92682E399B26518A95506B52C03BC9379A9DADF3391A21FB0EA4",
s: "414A718ED3249FF6DBC5B50C27F71F01F070944DA22AB1F78F559AAB",
},
ecdsaFixture{
name: "P224/SHA-512 #2",
key: p224,
alg: sha512.New,
message: "test",
r: "049F050477C5ADD858CAC56208394B5A55BAEBBE887FDF765047C17C",
s: "077EB13E7005929CEFA3CD0403C7CDCC077ADF4E44F3C41B2F60ECFF",
},
// ECDSA, 256 Bits (Prime Field)
// https://tools.ietf.org/html/rfc6979#appendix-A.2.5
ecdsaFixture{
name: "P256/SHA-1 #1",
key: p256,
alg: sha1.New,
message: "sample",
r: "61340C88C3AAEBEB4F6D667F672CA9759A6CCAA9FA8811313039EE4A35471D32",
s: "6D7F147DAC089441BB2E2FE8F7A3FA264B9C475098FDCF6E00D7C996E1B8B7EB",
},
ecdsaFixture{
name: "P256/SHA-224 #1",
key: p256,
alg: sha256.New224,
message: "sample",
r: "53B2FFF5D1752B2C689DF257C04C40A587FABABB3F6FC2702F1343AF7CA9AA3F",
s: "B9AFB64FDC03DC1A131C7D2386D11E349F070AA432A4ACC918BEA988BF75C74C",
},
ecdsaFixture{
name: "P256/SHA-256 #1",
key: p256,
alg: sha256.New,
message: "sample",
r: "EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716",
s: "F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8",
},
ecdsaFixture{
name: "P256/SHA-384 #1",
key: p256,
alg: sha512.New384,
message: "sample",
r: "0EAFEA039B20E9B42309FB1D89E213057CBF973DC0CFC8F129EDDDC800EF7719",
s: "4861F0491E6998B9455193E34E7B0D284DDD7149A74B95B9261F13ABDE940954",
},
ecdsaFixture{
name: "P256/SHA-512 #1",
key: p256,
alg: sha512.New,
message: "sample",
r: "8496A60B5E9B47C825488827E0495B0E3FA109EC4568FD3F8D1097678EB97F00",
s: "2362AB1ADBE2B8ADF9CB9EDAB740EA6049C028114F2460F96554F61FAE3302FE",
},
ecdsaFixture{
name: "P256/SHA-1 #2",
key: p256,
alg: sha1.New,
message: "test",
r: "0CBCC86FD6ABD1D99E703E1EC50069EE5C0B4BA4B9AC60E409E8EC5910D81A89",
s: "01B9D7B73DFAA60D5651EC4591A0136F87653E0FD780C3B1BC872FFDEAE479B1",
},
ecdsaFixture{
name: "P256/SHA-224 #2",
key: p256,
alg: sha256.New224,
message: "test",
r: "C37EDB6F0AE79D47C3C27E962FA269BB4F441770357E114EE511F662EC34A692",
s: "C820053A05791E521FCAAD6042D40AEA1D6B1A540138558F47D0719800E18F2D",
},
ecdsaFixture{
name: "P256/SHA-256 #2",
key: p256,
alg: sha256.New,
message: "test",
r: "F1ABB023518351CD71D881567B1EA663ED3EFCF6C5132B354F28D3B0B7D38367",
s: "019F4113742A2B14BD25926B49C649155F267E60D3814B4C0CC84250E46F0083",
},
ecdsaFixture{
name: "P256/SHA-384 #2",
key: p256,
alg: sha512.New384,
message: "test",
r: "83910E8B48BB0C74244EBDF7F07A1C5413D61472BD941EF3920E623FBCCEBEB6",
s: "8DDBEC54CF8CD5874883841D712142A56A8D0F218F5003CB0296B6B509619F2C",
},
ecdsaFixture{
name: "P256/SHA-512 #2",
key: p256,
alg: sha512.New,
message: "test",
r: "461D93F31B6540894788FD206C07CFA0CC35F46FA3C91816FFF1040AD1581A04",
s: "39AF9F15DE0DB8D97E72719C74820D304CE5226E32DEDAE67519E840D1194E55",
},
// ECDSA, 384 Bits (Prime Field)
// https://tools.ietf.org/html/rfc6979#appendix-A.2.6
ecdsaFixture{
name: "P384/SHA-1 #1",
key: p384,
alg: sha1.New,
message: "sample",
r: "EC748D839243D6FBEF4FC5C4859A7DFFD7F3ABDDF72014540C16D73309834FA37B9BA002899F6FDA3A4A9386790D4EB2",
s: "A3BCFA947BEEF4732BF247AC17F71676CB31A847B9FF0CBC9C9ED4C1A5B3FACF26F49CA031D4857570CCB5CA4424A443",
},
ecdsaFixture{
name: "P384/SHA-224 #1",
key: p384,
alg: sha256.New224,
message: "sample",
r: "42356E76B55A6D9B4631C865445DBE54E056D3B3431766D0509244793C3F9366450F76EE3DE43F5A125333A6BE060122",
s: "9DA0C81787064021E78DF658F2FBB0B042BF304665DB721F077A4298B095E4834C082C03D83028EFBF93A3C23940CA8D",
},
ecdsaFixture{
name: "P384/SHA-256 #1",
key: p384,
alg: sha256.New,
message: "sample",
r: "21B13D1E013C7FA1392D03C5F99AF8B30C570C6F98D4EA8E354B63A21D3DAA33BDE1E888E63355D92FA2B3C36D8FB2CD",
s: "F3AA443FB107745BF4BD77CB3891674632068A10CA67E3D45DB2266FA7D1FEEBEFDC63ECCD1AC42EC0CB8668A4FA0AB0",
},
ecdsaFixture{
name: "P384/SHA-384 #1",
key: p384,
alg: sha512.New384,
message: "sample",
r: "94EDBB92A5ECB8AAD4736E56C691916B3F88140666CE9FA73D64C4EA95AD133C81A648152E44ACF96E36DD1E80FABE46",
s: "99EF4AEB15F178CEA1FE40DB2603138F130E740A19624526203B6351D0A3A94FA329C145786E679E7B82C71A38628AC8",
},
ecdsaFixture{
name: "P384/SHA-512 #1",
key: p384,
alg: sha512.New,
message: "sample",
r: "ED0959D5880AB2D869AE7F6C2915C6D60F96507F9CB3E047C0046861DA4A799CFE30F35CC900056D7C99CD7882433709",
s: "512C8CCEEE3890A84058CE1E22DBC2198F42323CE8ACA9135329F03C068E5112DC7CC3EF3446DEFCEB01A45C2667FDD5",
},
ecdsaFixture{
name: "P384/SHA-1 #2",
key: p384,
alg: sha1.New,
message: "test",
r: "4BC35D3A50EF4E30576F58CD96CE6BF638025EE624004A1F7789A8B8E43D0678ACD9D29876DAF46638645F7F404B11C7",
s: "D5A6326C494ED3FF614703878961C0FDE7B2C278F9A65FD8C4B7186201A2991695BA1C84541327E966FA7B50F7382282",
},
ecdsaFixture{
name: "P384/SHA-224 #2",
key: p384,
alg: sha256.New224,
message: "test",
r: "E8C9D0B6EA72A0E7837FEA1D14A1A9557F29FAA45D3E7EE888FC5BF954B5E62464A9A817C47FF78B8C11066B24080E72",
s: "07041D4A7A0379AC7232FF72E6F77B6DDB8F09B16CCE0EC3286B2BD43FA8C6141C53EA5ABEF0D8231077A04540A96B66",
},
ecdsaFixture{
name: "P384/SHA-256 #2",
key: p384,
alg: sha256.New,
message: "test",
r: "6D6DEFAC9AB64DABAFE36C6BF510352A4CC27001263638E5B16D9BB51D451559F918EEDAF2293BE5B475CC8F0188636B",
s: "2D46F3BECBCC523D5F1A1256BF0C9B024D879BA9E838144C8BA6BAEB4B53B47D51AB373F9845C0514EEFB14024787265",
},
ecdsaFixture{
name: "P384/SHA-384 #2",
key: p384,
alg: sha512.New384,
message: "test",
r: "8203B63D3C853E8D77227FB377BCF7B7B772E97892A80F36AB775D509D7A5FEB0542A7F0812998DA8F1DD3CA3CF023DB",
s: "DDD0760448D42D8A43AF45AF836FCE4DE8BE06B485E9B61B827C2F13173923E06A739F040649A667BF3B828246BAA5A5",
},
ecdsaFixture{
name: "P384/SHA-512 #2",
key: p384,
alg: sha512.New,
message: "test",
r: "A0D5D090C9980FAF3C2CE57B7AE951D31977DD11C775D314AF55F76C676447D06FB6495CD21B4B6E340FC236584FB277",
s: "976984E59B4C77B0E8E4460DCA3D9F20E07B9BB1F63BEEFAF576F6B2E8B224634A2092CD3792E0159AD9CEE37659C736",
},
// ECDSA, 521 Bits (Prime Field)
// https://tools.ietf.org/html/rfc6979#appendix-A.2.7
ecdsaFixture{
name: "P521/SHA-1 #1",
key: p521,
alg: sha1.New,
message: "sample",
r: "0343B6EC45728975EA5CBA6659BBB6062A5FF89EEA58BE3C80B619F322C87910FE092F7D45BB0F8EEE01ED3F20BABEC079D202AE677B243AB40B5431D497C55D75D",
s: "0E7B0E675A9B24413D448B8CC119D2BF7B2D2DF032741C096634D6D65D0DBE3D5694625FB9E8104D3B842C1B0E2D0B98BEA19341E8676AEF66AE4EBA3D5475D5D16",
},
ecdsaFixture{
name: "P521/SHA-224 #1",
key: p521,
alg: sha256.New224,
message: "sample",
r: "1776331CFCDF927D666E032E00CF776187BC9FDD8E69D0DABB4109FFE1B5E2A30715F4CC923A4A5E94D2503E9ACFED92857B7F31D7152E0F8C00C15FF3D87E2ED2E",
s: "050CB5265417FE2320BBB5A122B8E1A32BD699089851128E360E620A30C7E17BA41A666AF126CE100E5799B153B60528D5300D08489CA9178FB610A2006C254B41F",
},
ecdsaFixture{
name: "P521/SHA-256 #1",
key: p521,
alg: sha256.New,
message: "sample",
r: "1511BB4D675114FE266FC4372B87682BAECC01D3CC62CF2303C92B3526012659D16876E25C7C1E57648F23B73564D67F61C6F14D527D54972810421E7D87589E1A7",
s: "04A171143A83163D6DF460AAF61522695F207A58B95C0644D87E52AA1A347916E4F7A72930B1BC06DBE22CE3F58264AFD23704CBB63B29B931F7DE6C9D949A7ECFC",
},
ecdsaFixture{
name: "P521/SHA-384 #1",
key: p521,
alg: sha512.New384,
message: "sample",
r: "1EA842A0E17D2DE4F92C15315C63DDF72685C18195C2BB95E572B9C5136CA4B4B576AD712A52BE9730627D16054BA40CC0B8D3FF035B12AE75168397F5D50C67451",
s: "1F21A3CEE066E1961025FB048BD5FE2B7924D0CD797BABE0A83B66F1E35EEAF5FDE143FA85DC394A7DEE766523393784484BDF3E00114A1C857CDE1AA203DB65D61",
},
ecdsaFixture{
name: "P521/SHA-512 #1",
key: p521,
alg: sha512.New,
message: "sample",
r: "0C328FAFCBD79DD77850370C46325D987CB525569FB63C5D3BC53950E6D4C5F174E25A1EE9017B5D450606ADD152B534931D7D4E8455CC91F9B15BF05EC36E377FA",
s: "0617CCE7CF5064806C467F678D3B4080D6F1CC50AF26CA209417308281B68AF282623EAA63E5B5C0723D8B8C37FF0777B1A20F8CCB1DCCC43997F1EE0E44DA4A67A",
},
ecdsaFixture{
name: "P521/SHA-1 #2",
key: p521,
alg: sha1.New,
message: "test",
r: "13BAD9F29ABE20DE37EBEB823C252CA0F63361284015A3BF430A46AAA80B87B0693F0694BD88AFE4E661FC33B094CD3B7963BED5A727ED8BD6A3A202ABE009D0367",
s: "1E9BB81FF7944CA409AD138DBBEE228E1AFCC0C890FC78EC8604639CB0DBDC90F717A99EAD9D272855D00162EE9527567DD6A92CBD629805C0445282BBC916797FF",
},
ecdsaFixture{
name: "P521/SHA-224 #2",
key: p521,
alg: sha256.New224,
message: "test",
r: "1C7ED902E123E6815546065A2C4AF977B22AA8EADDB68B2C1110E7EA44D42086BFE4A34B67DDC0E17E96536E358219B23A706C6A6E16BA77B65E1C595D43CAE17FB",
s: "177336676304FCB343CE028B38E7B4FBA76C1C1B277DA18CAD2A8478B2A9A9F5BEC0F3BA04F35DB3E4263569EC6AADE8C92746E4C82F8299AE1B8F1739F8FD519A4",
},
ecdsaFixture{
name: "P521/SHA-256 #2",
key: p521,
alg: sha256.New,
message: "test",
r: "00E871C4A14F993C6C7369501900C4BC1E9C7B0B4BA44E04868B30B41D8071042EB28C4C250411D0CE08CD197E4188EA4876F279F90B3D8D74A3C76E6F1E4656AA8",
s: "0CD52DBAA33B063C3A6CD8058A1FB0A46A4754B034FCC644766CA14DA8CA5CA9FDE00E88C1AD60CCBA759025299079D7A427EC3CC5B619BFBC828E7769BCD694E86",
},
ecdsaFixture{
name: "P521/SHA-384 #2",
key: p521,
alg: sha512.New384,
message: "test",
r: "14BEE21A18B6D8B3C93FAB08D43E739707953244FDBE924FA926D76669E7AC8C89DF62ED8975C2D8397A65A49DCC09F6B0AC62272741924D479354D74FF6075578C",
s: "133330865C067A0EAF72362A65E2D7BC4E461E8C8995C3B6226A21BD1AA78F0ED94FE536A0DCA35534F0CD1510C41525D163FE9D74D134881E35141ED5E8E95B979",
},
ecdsaFixture{
name: "P521/SHA-512 #2",
key: p521,
alg: sha512.New,
message: "test",
r: "13E99020ABF5CEE7525D16B69B229652AB6BDF2AFFCAEF38773B4B7D08725F10CDB93482FDCC54EDCEE91ECA4166B2A7C6265EF0CE2BD7051B7CEF945BABD47EE6D",
s: "1FBD0013C674AA79CB39849527916CE301C66EA7CE8B80682786AD60F98F7E78A19CA69EFF5C57400E3B3A0AD66CE0978214D13BAF4E9AC60752F7B155E2DE4DCE3",
},
}
func TestECDSA(t *testing.T) {
for _, f := range fixtures {
testEcsaFixture(&f, t)
}
}
func ecdsaLoadInt(s string) (n *big.Int) {
n, _ = new(big.Int).SetString(s, 16)
return
}
func testEcsaFixture(f *ecdsaFixture, t *testing.T) {
t.Logf("Testing %s", f.name)
h := f.alg()
h.Write([]byte(f.message))
digest := h.Sum(nil)
g := f.key.subgroup / 8
if len(digest) > g {
digest = digest[0:g]
}
r, s, err := rfc6979.SignECDSA(f.key.key, digest, f.alg)
if err != nil {
t.Error(err)
return
}
expectedR := ecdsaLoadInt(f.r)
expectedS := ecdsaLoadInt(f.s)
if r.Cmp(expectedR) != 0 {
t.Errorf("%s: Expected R of %X, got %X", f.name, expectedR, r)
}
if s.Cmp(expectedS) != 0 {
t.Errorf("%s: Expected S of %X, got %X", f.name, expectedS, s)
}
}

View file

@ -0,0 +1,76 @@
package rfc6979
import (
"crypto/dsa"
"crypto/ecdsa"
"crypto/rand"
"crypto/sha1"
"crypto/sha512"
"fmt"
"github.com/CityOfZion/neo-go/pkg/crypto/elliptic"
)
// Generates a 521-bit ECDSA key, uses SHA-512 to sign a message, then verifies
// it.
func ExampleSignECDSA() {
// Generate a key pair.
// You need a high-quality PRNG for this.
curve := elliptic.NewEllipticCurve(elliptic.Secp256r1)
k, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
fmt.Println(err)
return
}
// Hash a message.
alg := sha512.New()
alg.Write([]byte("I am a potato."))
hash := alg.Sum(nil)
// Sign the message. You don't need a PRNG for this.
r, s, err := SignECDSA(curve, k.D.Bytes(), hash, sha512.New)
if err != nil {
fmt.Println(err)
return
}
if !ecdsa.Verify(&k.PublicKey, hash, r, s) {
fmt.Println("Invalid signature!")
}
}
// Generates a 1024-bit DSA key, uses SHA-1 to sign a message, then verifies it.
func ExampleSignDSA() {
// Here I'm generating some DSA params, but you should really pre-generate
// these and re-use them, since this takes a long time and isn't necessary.
k := new(dsa.PrivateKey)
dsa.GenerateParameters(&k.Parameters, rand.Reader, dsa.L1024N160)
// Generate a key pair.
// You need a high-quality PRNG for this.
err := dsa.GenerateKey(k, rand.Reader)
if err != nil {
fmt.Println(err)
return
}
// Hash a message.
alg := sha1.New()
alg.Write([]byte("I am a potato."))
hash := alg.Sum(nil)
// Sign the message. You don't need a PRNG for this.
r, s, err := SignDSA(k, hash, sha1.New)
if err != nil {
fmt.Println(err)
return
}
if !dsa.Verify(&k.PublicKey, hash, r, s) {
fmt.Println("Invalid signature!")
}
}

119
pkg/crypto/rfc6979/rfc6979.go Executable file
View file

@ -0,0 +1,119 @@
/*
Package rfc6979 is an implementation of RFC 6979's deterministic DSA.
Such signatures are compatible with standard Digital Signature Algorithm
(DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA) digital
signatures and can be processed with unmodified verifiers, which need not be
aware of the procedure described therein. Deterministic signatures retain
the cryptographic security features associated with digital signatures but
can be more easily implemented in various environments, since they do not
need access to a source of high-quality randomness.
(https://tools.ietf.org/html/rfc6979)
Provides functions similar to crypto/dsa and crypto/ecdsa.
*/
package rfc6979
import (
"bytes"
"crypto/hmac"
"hash"
"math/big"
)
// mac returns an HMAC of the given key and message.
func mac(alg func() hash.Hash, k, m, buf []byte) []byte {
h := hmac.New(alg, k)
h.Write(m)
return h.Sum(buf[:0])
}
// https://tools.ietf.org/html/rfc6979#section-2.3.2
func bits2int(in []byte, qlen int) *big.Int {
vlen := len(in) * 8
v := new(big.Int).SetBytes(in)
if vlen > qlen {
v = new(big.Int).Rsh(v, uint(vlen-qlen))
}
return v
}
// https://tools.ietf.org/html/rfc6979#section-2.3.3
func int2octets(v *big.Int, rolen int) []byte {
out := v.Bytes()
// pad with zeros if it's too short
if len(out) < rolen {
out2 := make([]byte, rolen)
copy(out2[rolen-len(out):], out)
return out2
}
// drop most significant bytes if it's too long
if len(out) > rolen {
out2 := make([]byte, rolen)
copy(out2, out[len(out)-rolen:])
return out2
}
return out
}
// https://tools.ietf.org/html/rfc6979#section-2.3.4
func bits2octets(in []byte, q *big.Int, qlen, rolen int) []byte {
z1 := bits2int(in, qlen)
z2 := new(big.Int).Sub(z1, q)
if z2.Sign() < 0 {
return int2octets(z1, rolen)
}
return int2octets(z2, rolen)
}
var one = big.NewInt(1)
// https://tools.ietf.org/html/rfc6979#section-3.2
func generateSecret(q, x *big.Int, alg func() hash.Hash, hash []byte, test func(*big.Int) bool) {
qlen := q.BitLen()
holen := alg().Size()
rolen := (qlen + 7) >> 3
bx := append(int2octets(x, rolen), bits2octets(hash, q, qlen, rolen)...)
// Step B
v := bytes.Repeat([]byte{0x01}, holen)
// Step C
k := bytes.Repeat([]byte{0x00}, holen)
// Step D
k = mac(alg, k, append(append(v, 0x00), bx...), k)
// Step E
v = mac(alg, k, v, v)
// Step F
k = mac(alg, k, append(append(v, 0x01), bx...), k)
// Step G
v = mac(alg, k, v, v)
// Step H
for {
// Step H1
var t []byte
// Step H2
for len(t) < qlen/8 {
v = mac(alg, k, v, v)
t = append(t, v...)
}
// Step H3
secret := bits2int(t, qlen)
if secret.Cmp(one) >= 0 && secret.Cmp(q) < 0 && test(secret) {
return
}
k = mac(alg, k, append(v, 0x00), k)
v = mac(alg, k, v, v)
}
}

View file

@ -0,0 +1,28 @@
package rfc6979
import (
"crypto/sha256"
"encoding/hex"
"math/big"
"testing"
)
// https://tools.ietf.org/html/rfc6979#appendix-A.1
func TestGenerateSecret(t *testing.T) {
q, _ := new(big.Int).SetString("4000000000000000000020108A2E0CC0D99F8A5EF", 16)
x, _ := new(big.Int).SetString("09A4D6792295A7F730FC3F2B49CBC0F62E862272F", 16)
hash, _ := hex.DecodeString("AF2BDBE1AA9B6EC1E2ADE1D694F41FC71A831D0268E9891562113D8A62ADD1BF")
expected, _ := new(big.Int).SetString("23AF4074C90A02B3FE61D286D5C87F425E6BDD81B", 16)
var actual *big.Int
generateSecret(q, x, sha256.New, hash, func(k *big.Int) bool {
actual = k
return true
})
if actual.Cmp(expected) != 0 {
t.Errorf("Expected %x, got %x", expected, actual)
}
}

143
pkg/database/leveldb.go Normal file
View file

@ -0,0 +1,143 @@
package database
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/CityOfZion/neo-go/pkg/wire/payload"
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
"github.com/CityOfZion/neo-go/pkg/wire/util"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/errors"
)
type LDB struct {
db *leveldb.DB
path string
}
type Database interface {
Has(key []byte) (bool, error)
Put(key []byte, value []byte) error
Get(key []byte) ([]byte, error)
Delete(key []byte) error
Close() error
}
var (
// TX, HEADER AND UTXO are the prefixes for the db
TX = []byte("TX")
HEADER = []byte("HEADER")
LATESTHEADER = []byte("LH")
UTXO = []byte("UTXO")
)
func New(path string) *LDB {
db, err := leveldb.OpenFile(path, nil)
if _, corrupted := err.(*errors.ErrCorrupted); corrupted {
db, err = leveldb.RecoverFile(path, nil)
}
if err != nil {
return nil
}
return &LDB{
db,
path,
}
}
func (l *LDB) Has(key []byte) (bool, error) {
return l.db.Has(key, nil)
}
func (l *LDB) Put(key []byte, value []byte) error {
return l.db.Put(key, value, nil)
}
func (l *LDB) Get(key []byte) ([]byte, error) {
return l.db.Get(key, nil)
}
func (l *LDB) Delete(key []byte) error {
return l.db.Delete(key, nil)
}
func (l *LDB) Close() error {
return l.db.Close()
}
func (l *LDB) AddHeader(header *payload.BlockBase) error {
table := NewTable(l, HEADER)
byt, err := header.Bytes()
if err != nil {
fmt.Println("Could not Get bytes from decoded BlockBase")
return nil
}
fmt.Println("Adding Header, This should be batched!!!!")
// This is the main mapping
//Key: HEADER+BLOCKHASH Value: contents of blockhash
key := header.Hash.Bytes()
err = table.Put(key, byt)
if err != nil {
fmt.Println("Error trying to add the original mapping into the DB for Header. Mapping is [Header]+[Hash]")
return err
}
// This is the secondary mapping
// Key: HEADER + BLOCKHEIGHT Value: blockhash
bh := uint32ToBytes(header.Index)
key = []byte(bh)
err = table.Put(key, header.Hash.Bytes())
if err != nil {
return err
}
// This is the third mapping
// WARNING: This assumes that headers are adding in order.
return table.Put(LATESTHEADER, header.Hash.Bytes())
}
func (l *LDB) AddTransactions(blockhash util.Uint256, txs []transaction.Transactioner) error {
// SHOULD BE DONE IN BATCH!!!!
for i, tx := range txs {
buf := new(bytes.Buffer)
fmt.Println(tx.ID())
tx.Encode(buf)
txByt := buf.Bytes()
txhash, err := tx.ID()
if err != nil {
fmt.Println("Error adding transaction with bytes", txByt)
return err
}
// This is the original mapping
// Key: [TX] + TXHASH
key := append(TX, txhash.Bytes()...)
l.Put(key, txByt)
// This is the index
// Key: [TX] + BLOCKHASH + I <- i is the incrementer from the for loop
//Value : TXHASH
key = append(TX, blockhash.Bytes()...)
key = append(key, uint32ToBytes(uint32(i))...)
err = l.Put(key, txhash.Bytes())
if err != nil {
fmt.Println("Error could not add tx index into db")
return err
}
}
return nil
}
// BigEndian
func uint32ToBytes(h uint32) []byte {
a := make([]byte, 4)
binary.BigEndian.PutUint32(a, h)
return a
}

View file

@ -0,0 +1,80 @@
package database_test
import (
"os"
"testing"
"github.com/CityOfZion/neo-go/pkg/database"
"github.com/stretchr/testify/assert"
"github.com/syndtr/goleveldb/leveldb/errors"
)
const path = "temp"
func cleanup(db *database.LDB) {
db.Close()
os.RemoveAll(path)
}
func TestDBCreate(t *testing.T) {
db := database.New(path)
assert.NotEqual(t, nil, db)
cleanup(db)
}
func TestPutGet(t *testing.T) {
db := database.New(path)
key := []byte("Hello")
value := []byte("World")
err := db.Put(key, value)
assert.Equal(t, nil, err)
res, err := db.Get(key)
assert.Equal(t, nil, err)
assert.Equal(t, value, res)
cleanup(db)
}
func TestPutDelete(t *testing.T) {
db := database.New(path)
key := []byte("Hello")
value := []byte("World")
err := db.Put(key, value)
err = db.Delete(key)
assert.Equal(t, nil, err)
res, err := db.Get(key)
assert.Equal(t, errors.ErrNotFound, err)
assert.Equal(t, res, []byte{})
cleanup(db)
}
func TestHas(t *testing.T) {
db := database.New("temp")
res, err := db.Has([]byte("NotExist"))
assert.Equal(t, res, false)
assert.Equal(t, err, nil)
key := []byte("Hello")
value := []byte("World")
err = db.Put(key, value)
assert.Equal(t, nil, err)
res, err = db.Has(key)
assert.Equal(t, res, true)
assert.Equal(t, err, nil)
cleanup(db)
}
func TestDBClose(t *testing.T) {
db := database.New("temp")
err := db.Close()
assert.Equal(t, nil, err)
cleanup(db)
}

36
pkg/database/table.go Normal file
View file

@ -0,0 +1,36 @@
package database
//Table is an abstract datastructure built on
// top of a db
type Table struct {
prefix []byte
db Database
}
func NewTable(db Database, prefix []byte) *Table {
return &Table{
prefix,
db,
}
}
func (t *Table) Has(key []byte) (bool, error) {
key = append(t.prefix, key...)
return t.db.Has(key)
}
func (t *Table) Put(key []byte, value []byte) error {
key = append(t.prefix, key...)
return t.db.Put(key, value)
}
func (t *Table) Get(key []byte) ([]byte, error) {
key = append(t.prefix, key...)
return t.db.Get(key)
}
func (t *Table) Delete(key []byte) error {
key = append(t.prefix, key...)
return t.db.Delete(key)
}
func (t *Table) Close() error {
return nil
}

31
pkg/mempool/config.go Normal file
View file

@ -0,0 +1,31 @@
package mempool
import "time"
type Config struct {
// This is the maximum amount
// of transactions that we will allow in the mempool
MaxNumOfTX uint64
// FreeTX defines the maximum amount of free txs that can be in the mempool at one time
// Default is 20
FreeTX uint32
// MinTXFee is a number in Fixed8 format. If set at 1GAS, minTXFee would equal 1e8
// The mineTXFee is used to set the floor, it defaults to zero meaning we will allow all transactions
// with a fee of 0 or more
MinTXFee uint64
// MaxTXSize is the maximum number of bytes a tx can have to be entered into the pool
MaxTXSize uint64
// TXTTL is the duration to which we should keep an item in the mempool before removing it
// HMM: Should this be amount of blocks instead? For when blocks take time a long time
// to process?
TXTTL time.Duration
// SigLimit is the maximum amount of signatures
// that we will allow a tx to have, default will be 20
SigLimit uint8
}

138
pkg/mempool/mempool.go Normal file
View file

@ -0,0 +1,138 @@
package mempool
import (
"errors"
"fmt"
"sync"
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
var (
ErrMemPoolFull = errors.New("mempool is currently full")
ErrMempoolEmpty = errors.New("There are no TXs in the mempool")
ErrTXTooBig = errors.New("TX has exceed the maximum threshold")
ErrTXTooManyWitnesses = errors.New("Too many witness scripts")
ErrFeeTooLow = errors.New("Fee for transaction too low")
ErrDuplicateTX = errors.New("TX Already in pool")
)
type Mempool struct {
mtx sync.RWMutex
pool map[util.Uint256]*TX
cfg Config
}
func New(cfg Config) *Mempool {
mem := &Mempool{
sync.RWMutex{},
make(map[util.Uint256]*TX, 200),
cfg,
}
return mem
}
func (m *Mempool) AddTransaction(trans transaction.Transactioner) error {
hash, err := trans.ID()
if err != nil {
return err
}
// check if tx already in pool
if m.Exists(hash) {
return ErrDuplicateTX
}
m.mtx.Lock()
defer m.mtx.Unlock()
if m.cfg.MaxNumOfTX == uint64(len(m.pool)) {
return ErrMemPoolFull
}
// TODO:Check for double spend from blockchain itself
// create tx descriptor
tx := Descriptor(trans)
// check TX size
if tx.Size > m.cfg.MaxTXSize {
return ErrTXTooBig
}
// check witness length
if tx.NumWitness > m.cfg.SigLimit {
return ErrTXTooManyWitnesses
}
// TODO: check witness data is good -- Add method to take the Witness and tx return true or false.(blockchain)
//check fee is over minimum cnfigured
if tx.Fee < m.cfg.MinTXFee {
return ErrFeeTooLow
}
// Add into pool
m.pool[hash] = tx
return nil
}
// RemoveTransaction will remove a transaction from the nodes mempool
func (m *Mempool) RemoveTransaction(hash util.Uint256) error {
m.mtx.Lock()
defer m.mtx.Unlock()
if len(m.pool) == 0 {
return ErrMempoolEmpty
}
// deletes regardless of whether key is there or not. So do not check for existence before delete.
// Use Exists() for this.
delete(m.pool, hash)
return nil
}
// Size returns the size of the mempool
func (m *Mempool) Size() uint64 {
m.mtx.RLock()
len := uint64(len(m.pool))
m.mtx.RUnlock()
return len
}
// ReturnAllTransactions will return all transactions in the
// mempool, will be mostly used by the RPC server
func (m *Mempool) ReturnAllTransactions() ([]transaction.Transactioner, error) {
transactions := make([]transaction.Transactioner, 0)
m.mtx.RLock()
defer m.mtx.RUnlock()
if len(m.pool) == 0 {
return nil, ErrMempoolEmpty
}
for _, t := range m.pool {
if t.ParentTX == nil {
fmt.Println(t, "NILNIL")
}
transactions = append(transactions, *t.ParentTX)
fmt.Println(transactions)
}
return transactions, nil
}
// Exists check whether the transaction exists in the mempool
func (m *Mempool) Exists(hash util.Uint256) bool {
m.mtx.RLock()
_, ok := m.pool[hash]
m.mtx.RUnlock()
return ok
}

211
pkg/mempool/mempool_test.go Normal file
View file

@ -0,0 +1,211 @@
package mempool_test
import (
"testing"
"time"
"github.com/CityOfZion/neo-go/pkg/mempool"
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
"github.com/stretchr/testify/assert"
)
func TestMempoolExists(t *testing.T) {
cfg := mempool.Config{
MaxNumOfTX: 100,
FreeTX: 20,
MinTXFee: 0,
MaxTXSize: 10000,
TXTTL: 10 * time.Minute,
SigLimit: 20,
}
mem := mempool.New(cfg)
trans := transaction.NewContract(0)
assert.Equal(t, false, mem.Exists(trans.Hash))
err := mem.AddTransaction(trans)
assert.Equal(t, nil, err)
assert.Equal(t, true, mem.Exists(trans.Hash))
}
func TestMempoolFullPool(t *testing.T) {
maxTx := uint64(100)
cfg := mempool.Config{
MaxNumOfTX: maxTx,
FreeTX: 20,
MinTXFee: 0,
MaxTXSize: 10000,
TXTTL: 10 * time.Minute,
SigLimit: 20,
}
mem := mempool.New(cfg)
for i := uint64(1); i <= maxTx; i++ {
trans := transaction.NewContract(0)
attr := &transaction.Attribute{
Usage: transaction.Remark,
Data: []byte{byte(i)},
}
trans.AddAttribute(attr)
err := mem.AddTransaction(trans)
assert.Equal(t, nil, err)
}
trans := transaction.NewContract(0)
err := mem.AddTransaction(trans)
assert.NotEqual(t, nil, err)
assert.Equal(t, mempool.ErrMemPoolFull, err)
}
func TestMempoolLargeTX(t *testing.T) {
maxTxSize := uint64(100)
cfg := mempool.Config{
MaxNumOfTX: 100,
FreeTX: 20,
MinTXFee: 0,
MaxTXSize: maxTxSize,
TXTTL: 10 * time.Minute,
SigLimit: 20,
}
mem := mempool.New(cfg)
trans := transaction.NewContract(0)
for i := uint64(1); i <= 100; i++ { // 100 attributes will be over 100 bytes
attr := &transaction.Attribute{
Usage: transaction.Remark,
Data: []byte{byte(i)},
}
trans.AddAttribute(attr)
}
err := mem.AddTransaction(trans)
assert.NotEqual(t, nil, err)
assert.Equal(t, mempool.ErrTXTooBig, err)
}
func TestMempoolTooManyWitness(t *testing.T) {
maxWitness := uint8(3)
cfg := mempool.Config{
MaxNumOfTX: 100,
FreeTX: 20,
MinTXFee: 0,
MaxTXSize: 10000,
TXTTL: 10 * time.Minute,
SigLimit: maxWitness,
}
mem := mempool.New(cfg)
trans := transaction.NewContract(0)
for i := uint8(1); i <= maxWitness; i++ { // 100 attributes will be over 100 bytes
wit := &transaction.Witness{
InvocationScript: []byte{byte(i)},
VerificationScript: []byte{byte(i)},
}
trans.AddWitness(wit)
}
trans.AddWitness(&transaction.Witness{
InvocationScript: []byte{},
VerificationScript: []byte{},
})
err := mem.AddTransaction(trans)
assert.NotEqual(t, nil, err)
assert.Equal(t, mempool.ErrTXTooManyWitnesses, err)
}
func TestMempoolDuplicate(t *testing.T) {
cfg := mempool.Config{
MaxNumOfTX: 100,
FreeTX: 20,
MinTXFee: 0,
MaxTXSize: 10000,
TXTTL: 10 * time.Minute,
SigLimit: 1,
}
mem := mempool.New(cfg)
trans := transaction.NewContract(0)
err := mem.AddTransaction(trans)
assert.Equal(t, nil, err)
err = mem.AddTransaction(trans)
assert.NotEqual(t, nil, err)
assert.Equal(t, mempool.ErrDuplicateTX, err)
}
func TestMempoolReturnAll(t *testing.T) {
cfg := mempool.Config{
MaxNumOfTX: 100,
FreeTX: 20,
MinTXFee: 0,
MaxTXSize: 10000,
TXTTL: 10 * time.Minute,
SigLimit: 1,
}
mem := mempool.New(cfg)
numTx := uint64(10)
for i := uint64(1); i <= numTx; i++ {
trans := transaction.NewContract(0)
attr := &transaction.Attribute{
Usage: transaction.Remark,
Data: []byte{byte(i)},
}
trans.AddAttribute(attr)
err := mem.AddTransaction(trans)
assert.Equal(t, nil, err)
}
AllTrans, err := mem.ReturnAllTransactions()
assert.Equal(t, nil, err)
assert.Equal(t, numTx, uint64(len(AllTrans)))
}
func TestMempoolRemove(t *testing.T) {
cfg := mempool.Config{
MaxNumOfTX: 100,
FreeTX: 20,
MinTXFee: 0,
MaxTXSize: 10000,
TXTTL: 3 * time.Minute,
SigLimit: 1,
}
mem := mempool.New(cfg)
// Remove a transaction when mempool is empty
trans := transaction.NewContract(0)
hash, _ := trans.ID()
err := mem.RemoveTransaction(hash)
assert.Equal(t, mempool.ErrMempoolEmpty, err)
// Add tx1 into mempool
err = mem.AddTransaction(trans)
assert.Equal(t, nil, err)
diffTrans := transaction.NewContract(0) // TX2
diffTrans.AddAttribute(
&transaction.Attribute{
Usage: transaction.Remark,
Data: []byte{},
})
diffHash, _ := diffTrans.ID()
// Try removing TX2, when only TX1 is in mempool
err = mem.RemoveTransaction(diffHash)
assert.Equal(t, nil, err)
assert.Equal(t, uint64(1), mem.Size())
// Remove hash that is in mempool
err = mem.RemoveTransaction(hash)
assert.Equal(t, nil, err)
assert.Equal(t, uint64(0), mem.Size())
}

45
pkg/mempool/tx.go Normal file
View file

@ -0,0 +1,45 @@
package mempool
import (
"time"
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
)
// TX is a wrapper struct around a normal tx
// which includes extra information about the TX
// <aligned>
type TX struct {
ParentTX *transaction.Transactioner
Added time.Time
Fee uint64
Size uint64
NumWitness uint8
Free bool
}
// Descriptor takes a transaction and puts it into a new TX struct along with metadata
func Descriptor(trans transaction.Transactioner) *TX {
var desc TX
desc.ParentTX = &trans
desc.Fee = getFee(trans.TXOs(), trans.UTXOs())
desc.Free = desc.Fee != 0
desc.Added = time.Now()
desc.Size = uint64(len(trans.Bytes()))
numWit := len(trans.Witness())
if numWit > 255 || numWit < 0 { // < 0 should not happen
numWit = 255
}
desc.NumWitness = uint8(numWit)
return &desc
}
// TODO: need blockchain package complete for fee calculation
// HMM:Could also put the function in the config
func getFee(in []*transaction.Input, out []*transaction.Output) uint64 {
// query utxo set for inputs, then subtract from out to get fee
return 0
}

48
pkg/peer/config.go Normal file
View file

@ -0,0 +1,48 @@
package peer
import (
"github.com/CityOfZion/neo-go/pkg/wire/payload"
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
)
// LocalConfig specifies the properties that should be available for each remote peer
type LocalConfig struct {
Net protocol.Magic
UserAgent string
Services protocol.ServiceFlag
Nonce uint32
ProtocolVer protocol.Version
Relay bool
Port uint16
// pointer to config will keep the startheight updated for each version
//Message we plan to send
StartHeight func() uint32
OnHeader func(*Peer, *payload.HeadersMessage)
OnGetHeaders func(msg *payload.GetHeadersMessage) // returns HeaderMessage
OnAddr func(*Peer, *payload.AddrMessage)
OnGetAddr func(*Peer, *payload.GetAddrMessage)
OnInv func(*Peer, *payload.InvMessage)
OnGetData func(msg *payload.GetDataMessage)
OnBlock func(*Peer, *payload.BlockMessage)
OnGetBlocks func(msg *payload.GetBlocksMessage)
}
// func DefaultConfig() LocalConfig {
// return LocalConfig{
// Net: protocol.MainNet,
// UserAgent: "NEO-GO-Default",
// Services: protocol.NodePeerService,
// Nonce: 1200,
// ProtocolVer: 0,
// Relay: false,
// Port: 10332,
// // pointer to config will keep the startheight updated for each version
// //Message we plan to send
// StartHeight: DefaultHeight,
// }
// }
// func DefaultHeight() uint32 {
// return 10
// }

433
pkg/peer/peer.go Normal file
View file

@ -0,0 +1,433 @@
// This impl uses channels to simulate the queue handler with the actor model.
// A suitable number k ,should be set for channel size, because if #numOfMsg > k,
// we lose determinism. k chosen should be large enough that when filled, it shall indicate that
// the peer has stopped responding, since we do not have a pingMSG, we will need another way to shut down
// peers
package peer
import (
"errors"
"fmt"
"net"
"sync"
"sync/atomic"
"time"
"github.com/CityOfZion/neo-go/pkg/wire/command"
"github.com/CityOfZion/neo-go/pkg/peer/stall"
"github.com/CityOfZion/neo-go/pkg/wire"
"github.com/CityOfZion/neo-go/pkg/wire/payload"
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
const (
maxOutboundConnections = 100
protocolVer = protocol.DefaultVersion
handshakeTimeout = 30 * time.Second
idleTimeout = 5 * time.Minute // If no message received after idleTimeout, then peer disconnects
// nodes will have `responseTime` seconds to reply with a response
responseTime = 120 * time.Second
// the stall detector will check every `tickerInterval` to see if messages
// are overdue. Should be less than `responseTime`
tickerInterval = 30 * time.Second
// The input buffer size is the amount of mesages that
// can be buffered into the channel to receive at once before
// blocking, and before determinism is broken
inputBufferSize = 100
// The output buffer size is the amount of messages that
// can be buffered into the channel to send at once before
// blocking, and before determinism is broken.
outputBufferSize = 100
// pingInterval = 20 * time.Second //Not implemented in neo clients
)
var (
errHandShakeTimeout = errors.New("Handshake timed out, peers have " + string(handshakeTimeout) + " Seconds to Complete the handshake")
)
type Peer struct {
config LocalConfig
conn net.Conn
// atomic vals
disconnected int32
//unchangeable state: concurrent safe
addr string
protoVer protocol.Version
port uint16
inbound bool
userAgent string
services protocol.ServiceFlag
createdAt time.Time
relay bool
statemutex sync.Mutex
verackReceived bool
versionKnown bool
*stall.Detector
inch chan func() // will handle all incoming connections from peer
outch chan func() // will handle all outcoming connections from peer
quitch chan struct{}
}
func NewPeer(con net.Conn, inbound bool, cfg LocalConfig) *Peer {
p := Peer{}
p.inch = make(chan func(), inputBufferSize)
p.outch = make(chan func(), outputBufferSize)
p.quitch = make(chan struct{}, 1)
p.inbound = inbound
p.config = cfg
p.conn = con
p.createdAt = time.Now()
p.addr = p.conn.RemoteAddr().String()
p.Detector = stall.NewDetector(responseTime, tickerInterval)
// TODO: set the unchangeable states
return &p
}
// Write to a peer
func (p *Peer) Write(msg wire.Messager) error {
return wire.WriteMessage(p.conn, p.config.Net, msg)
}
// Read to a peer
func (p *Peer) Read() (wire.Messager, error) {
return wire.ReadMessage(p.conn, p.config.Net)
}
// Disconnects from a peer
func (p *Peer) Disconnect() {
// return if already disconnected
if atomic.LoadInt32(&p.disconnected) != 0 {
return
}
atomic.AddInt32(&p.disconnected, 1)
p.Detector.Quit()
close(p.quitch)
p.conn.Close()
fmt.Println("Disconnected Peer with address", p.RemoteAddr().String())
}
// Exposed API functions below
func (p *Peer) Port() uint16 {
return p.port
}
func (p *Peer) CreatedAt() time.Time {
return p.createdAt
}
func (p *Peer) CanRelay() bool {
return p.relay
}
func (p *Peer) LocalAddr() net.Addr {
return p.conn.LocalAddr()
}
func (p *Peer) RemoteAddr() net.Addr {
return p.conn.RemoteAddr()
}
func (p *Peer) Services() protocol.ServiceFlag {
return p.config.Services
}
func (p *Peer) Inbound() bool {
return p.inbound
}
func (p *Peer) UserAgent() string {
return p.config.UserAgent
}
func (p *Peer) IsVerackReceived() bool {
return p.verackReceived
}
func (p *Peer) NotifyDisconnect() bool {
fmt.Println("Peer has not disconnected yet")
<-p.quitch
fmt.Println("Peer has just disconnected")
return true
}
//End of Exposed API functions//
// Ping not impl. in neo yet, adding it now
// will cause this client to disconnect from all other implementations
func (p *Peer) PingLoop() { /*not implemented in other neo clients*/ }
// Run is used to start communicating with the peer
// completes the handshake and starts observing
// for messages coming in
func (p *Peer) Run() error {
err := p.Handshake()
go p.StartProtocol()
go p.ReadLoop()
go p.WriteLoop()
//go p.PingLoop() // since it is not implemented. It will disconnect all other impls.
return err
}
// run as a go-routine, will act as our queue for messages
// should be ran after handshake
func (p *Peer) StartProtocol() {
loop:
for atomic.LoadInt32(&p.disconnected) == 0 {
select {
case f := <-p.inch:
f()
case <-p.quitch:
break loop
case <-p.Detector.Quitch:
fmt.Println("Peer stalled, disconnecting")
break loop
}
}
p.Disconnect()
}
// Should only be called after handshake is complete
// on a seperate go-routine.
// ReadLoop Will block on the read until a message is
// read
func (p *Peer) ReadLoop() {
idleTimer := time.AfterFunc(idleTimeout, func() {
fmt.Println("Timing out peer")
p.Disconnect()
})
loop:
for atomic.LoadInt32(&p.disconnected) == 0 {
idleTimer.Reset(idleTimeout) // reset timer on each loop
readmsg, err := p.Read()
// Message read; stop Timer
idleTimer.Stop()
if err != nil {
fmt.Println("Err on read", err) // This will also happen if Peer is disconnected
break loop
}
// Remove message as pending from the stall detector
p.Detector.RemoveMessage(readmsg.Command())
switch msg := readmsg.(type) {
case *payload.VersionMessage:
fmt.Println("Already received a Version, disconnecting. " + p.RemoteAddr().String())
break loop // We have already done the handshake, break loop and disconnect
case *payload.VerackMessage:
if p.verackReceived {
fmt.Println("Already received a Verack, disconnecting. " + p.RemoteAddr().String())
break loop
}
p.statemutex.Lock() // This should not happen, however if it does, then we should set it.
p.verackReceived = true
p.statemutex.Unlock()
case *payload.AddrMessage:
p.OnAddr(msg)
case *payload.GetAddrMessage:
p.OnGetAddr(msg)
case *payload.GetBlocksMessage:
p.OnGetBlocks(msg)
case *payload.BlockMessage:
p.OnBlocks(msg)
case *payload.HeadersMessage:
p.OnHeaders(msg)
case *payload.GetHeadersMessage:
p.OnGetHeaders(msg)
case *payload.InvMessage:
p.OnInv(msg)
case *payload.GetDataMessage:
p.OnGetData(msg)
case *payload.TXMessage:
p.OnTX(msg)
default:
fmt.Println("Cannot recognise message", msg.Command()) //Do not disconnect peer, just Log Message
}
}
idleTimer.Stop()
p.Disconnect()
}
// WriteLoop will Queue all messages to be written to
// the peer.
func (p *Peer) WriteLoop() {
for atomic.LoadInt32(&p.disconnected) == 0 {
select {
case f := <-p.outch:
f()
case <-p.Detector.Quitch: // if the detector quits, disconnect peer
p.Disconnect()
}
}
}
func (p *Peer) OnGetData(msg *payload.GetDataMessage) {
p.inch <- func() {
// fmt.Println(msg.Hashes)
fmt.Println("That was an getdata Message please pass func down through config", msg.Command())
}
}
func (p *Peer) OnTX(msg *payload.TXMessage) {
p.inch <- func() {
// fmt.Println(msg.Hashes)
getdata, err := payload.NewGetDataMessage(payload.InvTypeTx)
if err != nil {
fmt.Println("Eor", err)
}
id, err := msg.Tx.ID()
getdata.AddHash(id)
p.Write(getdata)
fmt.Println("That was an tx Message please pass func down through config", msg.Command())
}
}
func (p *Peer) OnInv(msg *payload.InvMessage) {
p.inch <- func() {
if p.config.OnInv != nil {
p.config.OnInv(p, msg)
}
fmt.Println("That was an inv Message please pass func down through config", msg.Command())
}
}
// OnGetHeaders Listener, outside of the anonymous func will be extra functionality
// like timing
func (p *Peer) OnGetHeaders(msg *payload.GetHeadersMessage) {
p.inch <- func() {
if p.config.OnGetHeaders != nil {
p.config.OnGetHeaders(msg)
}
fmt.Println("That was a getheaders message, please pass func down through config", msg.Command())
}
}
// OnAddr Listener
func (p *Peer) OnAddr(msg *payload.AddrMessage) {
p.inch <- func() {
if p.config.OnAddr != nil {
p.config.OnAddr(p, msg)
}
fmt.Println("That was a addr message, please pass func down through config", msg.Command())
}
}
// OnGetAddr Listener
func (p *Peer) OnGetAddr(msg *payload.GetAddrMessage) {
p.inch <- func() {
if p.config.OnGetAddr != nil {
p.config.OnGetAddr(p, msg)
}
fmt.Println("That was a getaddr message, please pass func down through config", msg.Command())
}
}
// OnGetBlocks Listener
func (p *Peer) OnGetBlocks(msg *payload.GetBlocksMessage) {
p.inch <- func() {
if p.config.OnGetBlocks != nil {
p.config.OnGetBlocks(msg)
}
fmt.Println("That was a getblocks message, please pass func down through config", msg.Command())
}
}
// OnBlocks Listener
func (p *Peer) OnBlocks(msg *payload.BlockMessage) {
p.inch <- func() {
if p.config.OnBlock != nil {
p.config.OnBlock(p, msg)
}
}
}
// OnVersion Listener will be called
// during the handshake, any error checking should be done here for the versionMessage.
// This should only ever be called during the handshake. Any other place and the peer will disconnect.
func (p *Peer) OnVersion(msg *payload.VersionMessage) error {
if msg.Nonce == p.config.Nonce {
p.conn.Close()
return errors.New("Self connection, disconnecting Peer")
}
p.versionKnown = true
p.port = msg.Port
p.services = msg.Services
p.userAgent = string(msg.UserAgent)
p.createdAt = time.Now()
p.relay = msg.Relay
return nil
}
// OnHeaders Listener
func (p *Peer) OnHeaders(msg *payload.HeadersMessage) {
fmt.Println("We have received the headers")
p.inch <- func() {
if p.config.OnHeader != nil {
p.config.OnHeader(p, msg)
}
}
}
// RequestHeaders will write a getheaders to peer
func (p *Peer) RequestHeaders(hash util.Uint256) error {
fmt.Println("Sending header request")
c := make(chan error, 0)
p.outch <- func() {
p.Detector.AddMessage(command.GetHeaders)
getHeaders, err := payload.NewGetHeadersMessage([]util.Uint256{hash}, util.Uint256{})
err = p.Write(getHeaders)
c <- err
}
return <-c
}
// RequestBlocks will ask a peer for a block
func (p *Peer) RequestBlocks(hashes []util.Uint256) error {
fmt.Println("Requesting block from peer")
c := make(chan error, 0)
p.outch <- func() {
p.Detector.AddMessage(command.GetData)
getdata, err := payload.NewGetDataMessage(payload.InvTypeBlock)
err = getdata.AddHashes(hashes)
if err != nil {
c <- err
return
}
err = p.Write(getdata)
c <- err
}
return <-c
}

209
pkg/peer/peer_test.go Normal file
View file

@ -0,0 +1,209 @@
package peer_test
import (
"fmt"
"net"
"testing"
"time"
"github.com/CityOfZion/neo-go/pkg/peer"
"github.com/CityOfZion/neo-go/pkg/wire"
"github.com/CityOfZion/neo-go/pkg/wire/payload"
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
"github.com/stretchr/testify/assert"
)
func returnConfig() peer.LocalConfig {
DefaultHeight := func() uint32 {
return 10
}
OnAddr := func(p *peer.Peer, msg *payload.AddrMessage) {}
OnHeader := func(p *peer.Peer, msg *payload.HeadersMessage) {}
OnGetHeaders := func(msg *payload.GetHeadersMessage) {}
OnInv := func(p *peer.Peer, msg *payload.InvMessage) {}
OnGetData := func(msg *payload.GetDataMessage) {}
OnBlock := func(p *peer.Peer, msg *payload.BlockMessage) {}
OnGetBlocks := func(msg *payload.GetBlocksMessage) {}
return peer.LocalConfig{
Net: protocol.MainNet,
UserAgent: "NEO-GO-Default",
Services: protocol.NodePeerService,
Nonce: 1200,
ProtocolVer: 0,
Relay: false,
Port: 10332,
// pointer to config will keep the startheight updated for each version
//Message we plan to send
StartHeight: DefaultHeight,
OnHeader: OnHeader,
OnAddr: OnAddr,
OnGetHeaders: OnGetHeaders,
OnInv: OnInv,
OnGetData: OnGetData,
OnBlock: OnBlock,
OnGetBlocks: OnGetBlocks,
}
}
func TestHandshake(t *testing.T) {
address := ":20338"
go func() {
conn, err := net.DialTimeout("tcp", address, 2*time.Second)
if err != nil {
t.Fatal(err)
}
p := peer.NewPeer(conn, true, returnConfig())
err = p.Run()
verack, err := payload.NewVerackMessage()
if err != nil {
t.Fail()
}
if err := p.Write(verack); err != nil {
t.Fatal(err)
}
assert.Equal(t, true, p.IsVerackReceived())
}()
listener, err := net.Listen("tcp", address)
if err != nil {
t.Fatal(err)
return
}
defer func() {
listener.Close()
}()
for {
conn, err := listener.Accept()
if err != nil {
t.Fatal(err)
}
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP("82.2.97.142"), Port: 20338}
nonce := uint32(100)
messageVer, err := payload.NewVersionMessage(tcpAddrMe, 2595770, false, protocol.DefaultVersion, protocol.UserAgent, nonce, protocol.NodePeerService)
if err != nil {
t.Fatal(err)
}
if err := wire.WriteMessage(conn, protocol.MainNet, messageVer); err != nil {
t.Fatal(err)
return
}
readmsg, err := wire.ReadMessage(conn, protocol.MainNet)
if err != nil {
t.Fatal(err)
}
version, ok := readmsg.(*payload.VersionMessage)
if !ok {
t.Fatal(err)
}
assert.NotEqual(t, nil, version)
messageVrck, err := payload.NewVerackMessage()
if err != nil {
t.Fatal(err)
}
assert.NotEqual(t, nil, messageVrck)
if err := wire.WriteMessage(conn, protocol.MainNet, messageVrck); err != nil {
t.Fatal(err)
}
readmsg, err = wire.ReadMessage(conn, protocol.MainNet)
if err != nil {
t.Fatal(err)
}
assert.NotEqual(t, nil, readmsg)
verk, ok := readmsg.(*payload.VerackMessage)
if !ok {
t.Fatal(err)
}
assert.NotEqual(t, nil, verk)
return
}
}
func TestConfigurations(t *testing.T) {
_, conn := net.Pipe()
inbound := true
config := returnConfig()
p := peer.NewPeer(conn, inbound, config)
// test inbound
assert.Equal(t, inbound, p.Inbound())
// handshake not done, should be false
assert.Equal(t, false, p.IsVerackReceived())
assert.Equal(t, config.Services, p.Services())
assert.Equal(t, config.UserAgent, p.UserAgent())
assert.Equal(t, config.Relay, p.CanRelay())
assert.WithinDuration(t, time.Now(), p.CreatedAt(), 1*time.Second)
}
func TestHandshakeCancelled(t *testing.T) {
// These are the conditions which should invalidate the handshake.
// Make sure peer is disconnected.
}
func TestPeerDisconnect(t *testing.T) {
// Make sure everything is shutdown
// Make sure timer is shutdown in stall detector too. Should maybe put this part of test into stall detector.
_, conn := net.Pipe()
inbound := true
config := returnConfig()
p := peer.NewPeer(conn, inbound, config)
fmt.Println("Calling disconnect")
p.Disconnect()
fmt.Println("Disconnect finished calling")
verack, _ := payload.NewVerackMessage()
fmt.Println(" We good here")
err := p.Write(verack)
assert.NotEqual(t, err, nil)
// Check if Stall detector is still running
_, ok := <-p.Detector.Quitch
assert.Equal(t, ok, false)
}
func TestNotifyDisconnect(t *testing.T) {
_, conn := net.Pipe()
inbound := true
config := returnConfig()
p := peer.NewPeer(conn, inbound, config)
p.Disconnect()
p.NotifyDisconnect()
// TestNotify uses default test timeout as the passing condition
// Failure condition can be seen when you comment out p.Disconnect()
}

131
pkg/peer/peerhandshake.go Normal file
View file

@ -0,0 +1,131 @@
package peer
import (
"fmt"
"net"
"time"
"github.com/CityOfZion/neo-go/pkg/wire"
"github.com/CityOfZion/neo-go/pkg/wire/payload"
"github.com/CityOfZion/neo-go/pkg/wire/util/ip"
)
func (p *Peer) Handshake() error {
handshakeErr := make(chan error, 1)
go func() {
if p.inbound {
handshakeErr <- p.inboundHandShake()
} else {
handshakeErr <- p.outboundHandShake()
}
}()
select {
case err := <-handshakeErr:
if err != nil {
return err
}
case <-time.After(handshakeTimeout):
return errHandShakeTimeout
}
// This is purely here for Logs
if p.inbound {
fmt.Println("inbound handshake with", p.RemoteAddr().String(), "successful")
} else {
fmt.Println("outbound handshake with", p.RemoteAddr().String(), "successful")
}
return nil
}
// If this peer has an inbound conn (conn that is going into another peer)
// then he has dialed and so, we must read the version message
func (p *Peer) inboundHandShake() error {
var err error
if err := p.writeLocalVersionMSG(); err != nil {
return err
}
if err := p.readRemoteVersionMSG(); err != nil {
return err
}
verack, err := payload.NewVerackMessage()
if err != nil {
return err
}
err = p.Write(verack)
return p.readVerack()
}
func (p *Peer) outboundHandShake() error {
var err error
err = p.readRemoteVersionMSG()
if err != nil {
return err
}
err = p.writeLocalVersionMSG()
if err != nil {
return err
}
err = p.readVerack()
if err != nil {
return err
}
verack, err := payload.NewVerackMessage()
if err != nil {
return err
}
return p.Write(verack)
}
func (p *Peer) writeLocalVersionMSG() error {
nonce := p.config.Nonce
relay := p.config.Relay
port := int(p.config.Port)
ua := p.config.UserAgent
sh := p.config.StartHeight()
services := p.config.Services
proto := p.config.ProtocolVer
ip := iputils.GetLocalIP()
tcpAddrMe := &net.TCPAddr{IP: ip, Port: port}
messageVer, err := payload.NewVersionMessage(tcpAddrMe, sh, relay, proto, ua, nonce, services)
if err != nil {
return err
}
return p.Write(messageVer)
}
func (p *Peer) readRemoteVersionMSG() error {
readmsg, err := wire.ReadMessage(p.conn, p.config.Net)
if err != nil {
return err
}
version, ok := readmsg.(*payload.VersionMessage)
if !ok {
return err
}
return p.OnVersion(version)
}
func (p *Peer) readVerack() error {
readmsg, err := wire.ReadMessage(p.conn, p.config.Net)
if err != nil {
return err
}
_, ok := readmsg.(*payload.VerackMessage)
if !ok {
return err
}
// should only be accessed on one go-routine
p.verackReceived = true
return nil
}

67
pkg/peer/readme.md Normal file
View file

@ -0,0 +1,67 @@
# Package - Peer
## Responsibility
Once a connection has been made. The connection will represent a established peer to the localNode. Since a connection and the `Wire` is a golang primitive, that we cannot do much with. The peer package will encapsulate both, while adding extra functionality.
## Features
- The handshake protocol is automatically executed and handled by the peer package. If a Version/Verack is received twice, the peer will be disconnected.
- IdleTimeouts: If a Message is not received from the peer within a set period of time, the peer will be disconnected.
- StallTimeouts: For Example, If a GetHeaders, is sent to the Peer and a Headers Response is not received within a certain period of time, then the peer is disconnected.
- Concurrency Model: The concurrency model used is similar to Actor model, with a few changes. Messages can be sent to a peer asynchronously or synchronously. An example of an synchornous message send is the `RequestHeaders` method, where the channel blocks until an error value is received. The `OnHeaders` message is however asynchronously called. Furthermore, all methods passed through the config, are wrapped inside of an additional `Peers` method, this is to lay the ground work to capturing statistics regarding a specific command. These are also used so that we can pass behaviour to be executed down the channel.
- Configuration: Each Peer will have a config struct passed to it, with information about the Local Peer and functions that will encapsulate the behaviour of what the peer should do, given a request. This way, the peer is not dependent on any other package.
## Usage
conn, err := net.Dial("tcp", "seed2.neo.org:10333")
if err != nil {
fmt.Println("Error dialing connection", err.Error())
return
}
config := peer.LocalConfig{
Net: protocol.MainNet,
UserAgent: "NEO-G",
Services: protocol.NodePeerService,
Nonce: 1200,
ProtocolVer: 0,
Relay: false,
Port: 10332,
StartHeight: LocalHeight,
OnHeader: OnHeader,
}
p := peer.NewPeer(conn, false, config)
err = p.Run()
hash, err := util.Uint256DecodeString(chainparams.GenesisHash)
// hash2, err := util.Uint256DecodeString("ff8fe95efc5d1cc3a22b17503aecaf289cef68f94b79ddad6f613569ca2342d8")
err = p.RequestHeaders(hash)
func OnHeader(peer *peer.Peer, msg *payload.HeadersMessage) {
// This function is passed to peer
// and the peer will execute it on receiving a header
}
func LocalHeight() uint32 {
// This will be a function from the object that handles the block heights
return 10
}
### Notes
Should we follow the actor model for Peers? Each Peer will have a ID, which we can take as the PID or if
we launch a go-routine for each peer, then we can use that as an implicit PID.
Peer information should be stored into a database, if no db exists, we should get it from an initial peers file.
We can use this to periodically store information about a peer.

174
pkg/peer/stall/stall.go Normal file
View file

@ -0,0 +1,174 @@
package stall
import (
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/CityOfZion/neo-go/pkg/wire/command"
)
// stall detector will keep track of all pendingMessages
// If any message takes too long to reply
// the detector will disconnect the peer
type Detector struct {
responseTime time.Duration
tickInterval time.Duration
lock sync.Mutex
responses map[command.Type]time.Time
// The detector is embedded into a peer and the peer watches this quit chan
// If this chan is closed, the peer disconnects
Quitch chan struct{}
// atomic vals
disconnected int32
}
// rT is the responseTime and signals how long
// a peer has to reply back to a sent message
// tickerInterval is how often the detector wil check for stalled messages
func NewDetector(rTime time.Duration, tickerInterval time.Duration) *Detector {
d := &Detector{
responseTime: rTime,
tickInterval: tickerInterval,
lock: sync.Mutex{},
responses: map[command.Type]time.Time{},
Quitch: make(chan struct{}),
}
go d.loop()
return d
}
func (d *Detector) loop() {
ticker := time.NewTicker(d.tickInterval)
loop:
for {
select {
case <-ticker.C:
now := time.Now()
for _, deadline := range d.responses {
if now.After(deadline) {
fmt.Println("Deadline passed")
ticker.Stop()
break loop
}
}
}
}
d.Quit()
d.DeleteAll()
ticker.Stop()
}
// Quit is a concurrent safe way to call the Quit channel
// Without blocking
func (d *Detector) Quit() {
// return if already disconnected
if atomic.LoadInt32(&d.disconnected) != 0 {
return
}
atomic.AddInt32(&d.disconnected, 1)
close(d.Quitch)
}
// Call this function when we send a message to a peer
// The command passed through is the command that we sent
// and not the command we expect to receive
func (d *Detector) AddMessage(cmd command.Type) {
cmds := d.addMessage(cmd)
d.lock.Lock()
for _, cmd := range cmds {
d.responses[cmd] = time.Now().Add(d.responseTime)
}
d.lock.Unlock()
}
// Call this function when we receive a message from
// peer. This will remove the pendingresponse message from the map.
// The command passed through is the command we received
func (d *Detector) RemoveMessage(cmd command.Type) {
cmds := d.addMessage(cmd)
d.lock.Lock()
for _, cmd := range cmds {
delete(d.responses, cmd)
}
d.lock.Unlock()
}
// DeleteAll empties the map of all contents and
// is called when the detector is being shut down
func (d *Detector) DeleteAll() {
d.lock.Lock()
d.responses = make(map[command.Type]time.Time)
d.lock.Unlock()
}
// GetMessages Will return a map of all of the pendingResponses
// and their deadlines
func (d *Detector) GetMessages() map[command.Type]time.Time {
var resp map[command.Type]time.Time
d.lock.Lock()
resp = d.responses
d.lock.Unlock()
return resp
}
// when a message is added, we will add a deadline for
// expected response
func (d *Detector) addMessage(cmd command.Type) []command.Type {
cmds := []command.Type{}
switch cmd {
case command.GetHeaders:
// We now will expect a Headers Message
cmds = append(cmds, command.Headers)
case command.GetAddr:
// We now will expect a Headers Message
cmds = append(cmds, command.Addr)
case command.GetData:
// We will now expect a block/tx message
// We can optimise this by including the exact inventory type, however it is not needed
cmds = append(cmds, command.Block)
cmds = append(cmds, command.TX)
case command.GetBlocks:
// we will now expect a inv message
cmds = append(cmds, command.Inv)
case command.Version:
// We will now expect a verack
cmds = append(cmds, command.Verack)
}
return cmds
}
// if receive a message, we will delete it from pending
func (d *Detector) removeMessage(cmd command.Type) []command.Type {
cmds := []command.Type{}
switch cmd {
case command.Block:
// We will now expect a block/tx message
cmds = append(cmds, command.Block)
cmds = append(cmds, command.TX)
case command.TX:
// We will now expect a block/tx message
cmds = append(cmds, command.Block)
cmds = append(cmds, command.TX)
case command.GetBlocks:
// we will now expect a inv message
cmds = append(cmds, command.Inv)
default:
// We will now expect a verack
cmds = append(cmds, cmd)
}
return cmds
}

View file

@ -0,0 +1,75 @@
package stall
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/CityOfZion/neo-go/pkg/wire/command"
)
func TestAddRemoveMessage(t *testing.T) {
responseTime := 2 * time.Second
tickerInterval := 1 * time.Second
d := NewDetector(responseTime, tickerInterval)
d.AddMessage(command.GetAddr)
mp := d.GetMessages()
assert.Equal(t, 1, len(mp))
assert.IsType(t, time.Time{}, mp[command.GetAddr])
d.RemoveMessage(command.GetAddr)
mp = d.GetMessages()
assert.Equal(t, 0, len(mp))
assert.Empty(t, mp[command.GetAddr])
}
type mockPeer struct {
online bool
detector *Detector
}
func (mp *mockPeer) loop() {
loop:
for {
select {
case <-mp.detector.Quitch:
break loop
}
}
// cleanup
mp.online = false
}
func TestDeadlineWorks(t *testing.T) {
responseTime := 2 * time.Second
tickerInterval := 1 * time.Second
d := NewDetector(responseTime, tickerInterval)
mp := mockPeer{online: true, detector: d}
go mp.loop()
d.AddMessage(command.GetAddr)
time.Sleep(responseTime + 1*time.Second)
k := make(map[command.Type]time.Time)
assert.Equal(t, k, d.responses)
assert.Equal(t, false, mp.online)
}
func TestDeadlineShouldNotBeEmpty(t *testing.T) {
responseTime := 10 * time.Second
tickerInterval := 1 * time.Second
d := NewDetector(responseTime, tickerInterval)
d.AddMessage(command.GetAddr)
time.Sleep(1 * time.Second)
k := make(map[command.Type]time.Time)
assert.NotEqual(t, k, d.responses)
}

View file

@ -0,0 +1,67 @@
package peermanager
import (
"errors"
"fmt"
"github.com/CityOfZion/neo-go/pkg/peer"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
// NOTE: This package may be removed in the future
// and so full functionality is not yet implemented, see Issue #33 for more details.
//PeerMgr will act as a convenience Mgr
// It will be notified of added Peers
// It will take care of sending messages to the right peers. In this way, it acts as a load balancer
// If we send a getdata to one peer, it will be smart and send it to another peer who is not as busy
// Using subscription model, we can have the syncmanager/other modules notify the peermgr when they have received data
type PeerMgr struct {
peers []*peer.Peer
}
// New will create a new peer manager
// As of now it just returns a peerMgr struct and so
// the New method is redundant. A config file will be passed as a parameter,
// if it is decided that we will use this.
func New() *PeerMgr {
return &PeerMgr{}
}
// Disconnect will close the connection on a peer and
// remove it from the list
// TODO: remove from list once disconnected
func (pm *PeerMgr) Disconnect(p *peer.Peer) {
p.Disconnect()
// Once disconnected, we remove it from the list
// and look for more peers to connect to
}
// RequestHeaders will request the headers from the most available peer
// As of now, it requests from the first peer in the list, TODO(Kev)
func (pm *PeerMgr) RequestHeaders(hash util.Uint256) (*peer.Peer, error) {
if len(pm.peers) == 0 {
return nil, errors.New("Peer manager currently has no peers")
}
return pm.peers[0], pm.peers[0].RequestHeaders(hash)
}
// RequestBlocks will request blocks from the most available peer
// As of now, it requests from the first peer in the list, TODO(Kev)
func (pm *PeerMgr) RequestBlocks(hash []util.Uint256) (*peer.Peer, error) {
if len(pm.peers) == 0 {
return nil, errors.New("Peer manager currently has no peers")
}
return pm.peers[0], pm.peers[0].RequestBlocks(hash)
}
// AddPeer will add a new peer for the PeerManager to use
func (pm *PeerMgr) AddPeer(p *peer.Peer) error {
pm.peers = append(pm.peers, p)
fmt.Println("Adding peers into the peermanager")
return nil
}

14
pkg/pubsub/event.go Normal file
View file

@ -0,0 +1,14 @@
package pubsub
type EventType int
const (
NewBlock EventType = iota // When blockchain connects a new block, it will emit an NewBlock Event
BadBlock // When blockchain declines a block, it will emit a new block event
BadHeader // When blockchain rejects a Header, it will emit this event
)
type Event struct {
Type EventType // E.g. event.NewBlock
data []byte // Raw information
}

20
pkg/pubsub/pub.go Normal file
View file

@ -0,0 +1,20 @@
package pubsub
type Publisher struct {
subs []Subscriber
}
// Send iterates over each subscriber and checks
// if they are interested in the Event
// By looking at their topics, if they are then
// the event is emitted to them
func (p *Publisher) Send(e Event) error {
for _, sub := range p.subs {
for _, topic := range sub.Topics() {
if e.Type == topic {
sub.Emit(e)
}
}
}
return nil
}

6
pkg/pubsub/sub.go Normal file
View file

@ -0,0 +1,6 @@
package pubsub
type Subscriber interface {
Topics() []EventType
Emit(Event)
}

11
pkg/syncmanager/config.go Normal file
View file

@ -0,0 +1,11 @@
package syncmanager
import (
"github.com/CityOfZion/neo-go/pkg/blockchain"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
type Config struct {
Chain *blockchain.Chain
BestHash util.Uint256
}

152
pkg/syncmanager/syncman.go Normal file
View file

@ -0,0 +1,152 @@
// The syncmanager will use a modified verison of the initial block download in bitcoin
// Seen here: https://en.bitcoinwiki.org/wiki/Bitcoin_Core_0.11_(ch_5):_Initial_Block_Download
// MovingWindow is a desired featured from the original codebase
package syncmanager
import (
"fmt"
"github.com/CityOfZion/neo-go/pkg/peermanager"
"github.com/CityOfZion/neo-go/pkg/blockchain"
"github.com/CityOfZion/neo-go/pkg/peer"
"github.com/CityOfZion/neo-go/pkg/wire/payload"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
var (
// This is the maximum amount of inflight objects that we would like to have
// Number taken from original codebase
maxBlockRequest = 1024
// This is the maximum amount of blocks that we will ask for from a single peer
// Number taken from original codebase
maxBlockRequestPerPeer = 16
)
type Syncmanager struct {
pmgr *peermanager.PeerMgr
Mode int // 1 = headersFirst, 2 = Blocks, 3 = Maintain
chain *blockchain.Chain
headers []util.Uint256
inflightBlockReqs map[util.Uint256]*peer.Peer // when we send a req for block, we will put hash in here, along with peer who we requested it from
}
// New will setup the syncmanager with the required
// parameters
func New(cfg Config) *Syncmanager {
return &Syncmanager{
peermanager.New(),
1,
cfg.Chain,
[]util.Uint256{},
make(map[util.Uint256]*peer.Peer, 2000),
}
}
func (s *Syncmanager) AddPeer(peer *peer.Peer) error {
return s.pmgr.AddPeer(peer)
}
func (s *Syncmanager) OnHeaders(p *peer.Peer, msg *payload.HeadersMessage) {
fmt.Println("Sync manager On Headers called")
// On receipt of Headers
// check what mode we are in
// HeadersMode, we check if there is 2k. If so call again. If not then change mode into BlocksOnly
if s.Mode == 1 {
err := s.HeadersFirstMode(p, msg)
if err != nil {
fmt.Println("Error re blocks", err)
return // We should custom name error so, that we can do something on WrongHash Error, Peer disconnect error
}
return
}
}
func (s *Syncmanager) HeadersFirstMode(p *peer.Peer, msg *payload.HeadersMessage) error {
fmt.Println("Headers first mode")
// Validate Headers
err := s.chain.ValidateHeaders(msg)
if err != nil {
// Re-request headers from a different peer
s.pmgr.Disconnect(p)
fmt.Println("Error Validating headers", err)
return err
}
// Add Headers into db
err = s.chain.AddHeaders(msg)
if err != nil {
// Try addding them into the db again?
// Since this is simply a db insert, any problems here means trouble
//TODO(KEV) : Should we Switch off system or warn the user that the system is corrupted?
fmt.Println("Error Adding headers", err)
//TODO: Batching is not yet implemented,
// So here we would need to remove headers which have been added
// from the slice
return err
}
// Add header hashes into slice
// Requets first batch of blocks here
var hashes []util.Uint256
for _, header := range msg.Headers {
hashes = append(hashes, header.Hash)
}
s.headers = append(s.headers, hashes...)
if len(msg.Headers) == 2*1e3 { // should be less than 2000, leave it as this for tests
fmt.Println("Switching to BlocksOnly Mode")
s.Mode = 2 // switch to BlocksOnly. XXX: because HeadersFirst is not in parallel, no race condition here.
return s.RequestMoreBlocks()
}
lastHeader := msg.Headers[len(msg.Headers)-1]
_, err = s.pmgr.RequestHeaders(lastHeader.Hash)
return err
}
func (s *Syncmanager) RequestMoreBlocks() error {
var blockReq []util.Uint256
var reqAmount int
if len(s.headers) >= maxBlockRequestPerPeer {
reqAmount = maxBlockRequestPerPeer
blockReq = s.headers[:reqAmount]
} else {
reqAmount = len(s.headers)
blockReq = s.headers[:reqAmount]
}
peer, err := s.pmgr.RequestBlocks(blockReq)
if err != nil { // This could happen if the peermanager has no valid peers to connect to. We should wait a bit and re-request
return err // alternatively we could make RequestBlocks blocking, then make sure it is not triggered when a block is received
}
//XXX: Possible race condition, between us requesting the block and adding it to
// the inflight block map? Give that node a medal.
for _, hash := range s.headers {
s.inflightBlockReqs[hash] = peer
}
s.headers = s.headers[reqAmount:]
// NONONO: Here we do not pass all of the hashes to peermanager because
// it is not the peermanagers responsibility to mange inflight blocks
return err
}
// OnBlock receives a block from a peer, then passes it to the blockchain to process.
// For now we will only use this simple setup, to allow us to test the other parts of the system.
// See Issue #24
func (s *Syncmanager) OnBlock(p *peer.Peer, msg *payload.BlockMessage) {
err := s.chain.AddBlock(msg)
if err != nil {
// Put headers back in front of queue to fetch block for.
fmt.Println("Block had an error", err)
}
}

62
pkg/wire/Readme.md Normal file
View file

@ -0,0 +1,62 @@
# Package - Wire
The neo wire package will implement the network protocol displayed here: http://docs.neo.org/en-us/network/network-protocol.html
This package will act as a standalone package.
# Responsibility
This package will solely be responsible for Encoding and decoding a Message.
It will return a Messager interface, which means that the caller of the package will need to type assert it to the appropriate type.
# Usage
## Write Message
expectedIP := "127.0.0.1"
expectedPort := 8333
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort}
message, err := NewVersionMessage(tcpAddrMe, 0, true, defaultVersion)
conn := new(bytes.Buffer)
if err := WriteMessage(con, Production, message); err != nil {
// handle Error
}
## Read Message
readmsg, err := ReadMessage(conn, Production)
if err != nil {
// Log error
}
version := readmsg.(*VersionMessage)
## RoadMap
These below commands are left to implement.
[ x ] CMDVersion (Tests added)
[ x ] CMDVerack (Tests Added)
[ x ] CMDGetAddr(Tests Added)
[ x ] CMDAddr (Tests Added)
[ x ] CMDGetHeaders (Tests Added)
[ x ] CMDHeaders (Tests Added)
[ x ] CMDGetBlocks (Tests Added)
[ x ] CMDInv (Tests Added)
[ x ] CMDGetData (Tests Added)
[ x ] CMDBlock (Tests Added)
[ x ] CMDTX // Each tx implments the messager interface
[ ] CMDConsensus
## Notes
Please not that this package will do sanity checks on the fields, however it will not verify if any of the items are valid for the current state of the system. Please see `Responbilities`.
The difference between Encode/Decode and EncodePayload/DecodePayload, is the parameter type.
In most cases, Encode/Decode is just a convenience method.
# Contributors
When modifying this package, please ensure that it does not depend on any other package and that it conforms to the Single Responsibility Principle. If you see somewhere in the current implementation that does not do this, then please tell me.

44
pkg/wire/base.go Normal file
View file

@ -0,0 +1,44 @@
package wire
import (
"io"
"github.com/CityOfZion/neo-go/pkg/wire/command"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
// Base is everything in the message except the payload
type Base struct {
Magic uint32
CMD command.Type
PayloadLength uint32
Checksum uint32
}
// Note, That there is no EncodeBase
// As the header is implicitly inferred from
// the message on Encode To send
func (h *Base) DecodeBase(r io.Reader) (io.Reader, error) {
br := &util.BinReader{R: r}
br.Read(&h.Magic)
var cmd [12]byte
br.Read(&cmd)
h.CMD = command.Type(cmdByteArrayToString(cmd))
br.Read(&h.PayloadLength)
br.Read(&h.Checksum)
return br.R, br.Err
}
func cmdByteArrayToString(cmd [command.Size]byte) string {
buf := []byte{}
for i := 0; i < command.Size; i++ {
if cmd[i] != 0 {
buf = append(buf, cmd[i])
}
}
return string(buf)
}

View file

@ -0,0 +1,30 @@
package command
// Size of command field in bytes
const (
Size = 12
)
// CommandType represents the type of a message command.
type Type string
// Valid protocol commands used to send between nodes.
// use this to get
const (
Version Type = "version"
Mempool Type = "mempool"
Ping Type = "ping"
Pong Type = "pong"
Verack Type = "verack"
GetAddr Type = "getaddr"
Addr Type = "addr"
GetHeaders Type = "getheaders"
Headers Type = "headers"
GetBlocks Type = "getblocks"
Inv Type = "inv"
GetData Type = "getdata"
Block Type = "block"
TX Type = "tx"
Consensus Type = "consensus"
Unknown Type = "unknown"
)

148
pkg/wire/message.go Normal file
View file

@ -0,0 +1,148 @@
package wire
import (
"bufio"
"bytes"
"errors"
"io"
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
checksum "github.com/CityOfZion/neo-go/pkg/wire/util/Checksum"
"github.com/CityOfZion/neo-go/pkg/wire/command"
"github.com/CityOfZion/neo-go/pkg/wire/payload"
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
type Messager interface {
EncodePayload(w io.Writer) error
DecodePayload(r io.Reader) error
Command() command.Type
}
const (
// Magic + cmd + length + checksum
minMsgSize = 4 + 12 + 4 + 4
)
var (
errChecksumMismatch = errors.New("checksum mismatch")
)
func WriteMessage(w io.Writer, magic protocol.Magic, message Messager) error {
bw := &util.BinWriter{W: w}
bw.Write(magic)
bw.Write(cmdToByteArray(message.Command()))
buf := new(bytes.Buffer)
if err := message.EncodePayload(buf); err != nil {
return err
}
payloadLen := util.BufferLength(buf)
checksum := checksum.FromBytes(buf.Bytes())
bw.Write(payloadLen)
bw.Write(checksum)
bw.WriteBigEnd(buf.Bytes())
return bw.Err
}
func ReadMessage(r io.Reader, magic protocol.Magic) (Messager, error) {
byt := make([]byte, minMsgSize)
if _, err := io.ReadFull(r, byt); err != nil {
return nil, err
}
reader := bytes.NewReader(byt)
var header Base
_, err := header.DecodeBase(reader)
if err != nil {
return nil, errors.New("Error decoding into the header base")
}
buf := new(bytes.Buffer)
_, err = io.CopyN(buf, r, int64(header.PayloadLength))
if err != nil {
return nil, err
}
// Compare the checksum of the payload.
if !checksum.Compare(header.Checksum, buf.Bytes()) {
return nil, errChecksumMismatch
}
switch header.CMD {
case command.Version:
v := &payload.VersionMessage{}
err := v.DecodePayload(buf)
return v, err
case command.Verack:
v, err := payload.NewVerackMessage()
err = v.DecodePayload(buf)
return v, err
case command.Inv:
v, err := payload.NewInvMessage(0)
err = v.DecodePayload(buf)
return v, err
case command.GetAddr:
v, err := payload.NewGetAddrMessage()
err = v.DecodePayload(buf)
return v, err
case command.Addr:
v, err := payload.NewAddrMessage()
err = v.DecodePayload(buf)
return v, err
case command.Block:
v, err := payload.NewBlockMessage()
err = v.DecodePayload(buf)
return v, err
case command.GetBlocks:
v, err := payload.NewGetBlocksMessage([]util.Uint256{}, util.Uint256{})
err = v.DecodePayload(buf)
return v, err
case command.GetData:
v, err := payload.NewGetDataMessage(payload.InvTypeTx)
err = v.DecodePayload(buf)
return v, err
case command.GetHeaders:
v, err := payload.NewGetHeadersMessage([]util.Uint256{}, util.Uint256{})
err = v.DecodePayload(buf)
return v, err
case command.Headers:
v, err := payload.NewHeadersMessage()
err = v.DecodePayload(buf)
return v, err
case command.TX:
reader := bufio.NewReader(buf)
tx, err := transaction.FromBytes(reader)
if err != nil {
return nil, err
}
return payload.NewTXMessage(tx)
}
return nil, errors.New("Unknown Message found")
}
func cmdToByteArray(cmd command.Type) [command.Size]byte {
cmdLen := len(cmd)
if cmdLen > command.Size {
panic("exceeded command max length of size 12")
}
// The command can have max 12 bytes, rest is filled with 0.
b := [command.Size]byte{}
for i := 0; i < cmdLen; i++ {
b[i] = cmd[i]
}
return b
}

55
pkg/wire/message_test.go Normal file
View file

@ -0,0 +1,55 @@
package wire
import (
"bytes"
"net"
"testing"
"github.com/CityOfZion/neo-go/pkg/wire/payload"
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
"github.com/stretchr/testify/assert"
)
// This is quite hard to test because the message uses time.Now()
// TODO: Test each field expect time.Now(), just make sure it is a uint32
func TestWriteMessageLen(t *testing.T) {
expectedIP := "127.0.0.1"
expectedPort := 8333
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort}
message, err := payload.NewVersionMessage(tcpAddrMe, 0, true, protocol.DefaultVersion, protocol.UserAgent, 100, protocol.NodePeerService)
if err != nil {
assert.Fail(t, err.Error())
}
buf := new(bytes.Buffer)
if err := WriteMessage(buf, protocol.MainNet, message); err != nil {
assert.Fail(t, err.Error())
}
assert.Equal(t, 60, len(buf.Bytes()))
}
func TestReadMessage(t *testing.T) {
expectedIP := "127.0.0.1"
expectedPort := 8333
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort}
message, err := payload.NewVersionMessage(tcpAddrMe, 23, true, protocol.DefaultVersion, protocol.UserAgent, 100, protocol.NodePeerService)
if err != nil {
assert.Fail(t, err.Error())
}
buf := new(bytes.Buffer)
if err := WriteMessage(buf, protocol.MainNet, message); err != nil {
assert.Fail(t, err.Error())
}
readmsg, err := ReadMessage(buf, protocol.MainNet)
if err != nil {
assert.Fail(t, err.Error())
}
version := readmsg.(*payload.VersionMessage)
assert.Equal(t, 23, int(version.StartHeight))
// If MessageReading was unsuccessfull it will return a nil object
}

59
pkg/wire/payload/block.go Normal file
View file

@ -0,0 +1,59 @@
package payload
import (
"bufio"
"bytes"
"io"
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
type Block struct {
BlockBase
Txs []transaction.Transactioner
}
func (b *Block) Decode(r io.Reader) error {
br := &util.BinReader{R: r}
b.DecodePayload(br)
return br.Err
}
func (b *Block) Encode(w io.Writer) error {
bw := &util.BinWriter{W: w}
b.EncodePayload(bw)
return bw.Err
}
func (b *Block) EncodePayload(bw *util.BinWriter) {
b.BlockBase.EncodePayload(bw)
bw.VarUint(uint64(len(b.Txs)))
for _, tx := range b.Txs {
tx.Encode(bw.W)
}
}
func (b *Block) DecodePayload(br *util.BinReader) error {
b.BlockBase.DecodePayload(br)
lenTXs := br.VarUint()
b.Txs = make([]transaction.Transactioner, lenTXs)
reader := bufio.NewReader(br.R)
for i := 0; i < int(lenTXs); i++ {
tx, err := transaction.FromBytes(reader)
if err != nil {
return err
}
b.Txs[i] = tx
}
return nil
}
func (b *Block) Bytes() ([]byte, error) {
buf := new(bytes.Buffer)
err := b.Encode(buf)
return buf.Bytes(), err
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,124 @@
package payload
import (
"bytes"
"errors"
"io"
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
var (
ErrPadding = errors.New("There is a padding mismatch")
)
type BlockBase struct {
// Version of the block.
Version uint32 `json:"version"`
// hash of the previous block.
PrevHash util.Uint256 `json:"previousblockhash"`
// Root hash of a transaction list.
MerkleRoot util.Uint256 `json:"merkleroot"`
// The time stamp of each block must be later than previous block's time stamp.
// Generally the difference of two block's time stamp is about 15 seconds and imprecision is allowed.
// The height of the block must be exactly equal to the height of the previous block plus 1.
Timestamp uint32 `json:"time"`
// index/height of the block
Index uint32 `json:"height"`
// Random number also called nonce
ConsensusData uint64 `json:"nonce"`
// Contract addresss of the next miner
NextConsensus util.Uint160 `json:"next_consensus"`
// Padding that is fixed to 1
_ uint8
// Script used to validate the block
Witness transaction.Witness `json:"script"`
// hash of this block, created when binary encoded.
Hash util.Uint256
}
func (b *BlockBase) EncodePayload(bw *util.BinWriter) error {
b.encodeHashableFields(bw)
bw.Write(uint8(1))
b.Witness.Encode(bw)
return bw.Err
}
func (b *BlockBase) Decode(r io.Reader) error {
br := &util.BinReader{R: r}
b.DecodePayload(br)
return br.Err
}
func (b *BlockBase) Encode(w io.Writer) error {
bw := &util.BinWriter{W: w}
b.EncodePayload(bw)
return bw.Err
}
func (b *BlockBase) encodeHashableFields(bw *util.BinWriter) {
bw.Write(b.Version)
bw.Write(b.PrevHash)
bw.Write(b.MerkleRoot)
bw.Write(b.Timestamp)
bw.Write(b.Index)
bw.Write(b.ConsensusData)
bw.Write(b.NextConsensus)
}
func (b *BlockBase) DecodePayload(br *util.BinReader) error {
b.decodeHashableFields(br)
var padding uint8
br.Read(&padding)
if padding != 1 {
return ErrPadding
}
b.Witness = transaction.Witness{}
b.Witness.Decode(br)
err := b.createHash()
if err != nil {
return err
}
return br.Err
}
func (b *BlockBase) decodeHashableFields(br *util.BinReader) {
br.Read(&b.Version)
br.Read(&b.PrevHash)
br.Read(&b.MerkleRoot)
br.Read(&b.Timestamp)
br.Read(&b.Index)
br.Read(&b.ConsensusData)
br.Read(&b.NextConsensus)
}
func (b *BlockBase) createHash() error {
hash, err := util.CalculateHash(b.encodeHashableFields)
b.Hash = hash
return err
}
func (b *BlockBase) Bytes() ([]byte, error) {
buf := new(bytes.Buffer)
err := b.Encode(buf)
return buf.Bytes(), err
}

View file

@ -0,0 +1,8 @@
package payload
import "testing"
func Test(t *testing.T) {
//tests for this have been included in the mheaders_test file
}

61
pkg/wire/payload/maddr.go Normal file
View file

@ -0,0 +1,61 @@
package payload
import (
"io"
"github.com/CityOfZion/neo-go/pkg/wire/command"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
type AddrMessage struct {
AddrList []*Net_addr
}
func NewAddrMessage() (*AddrMessage, error) {
addrMess := &AddrMessage{
nil,
}
return addrMess, nil
}
func (a *AddrMessage) AddNetAddr(n *Net_addr) error {
a.AddrList = append(a.AddrList, n)
// TODO:check if max reached, if so return err. What is max?
return nil
}
// Implements Messager interface
func (a *AddrMessage) DecodePayload(r io.Reader) error {
br := &util.BinReader{R: r}
listLen := br.VarUint()
a.AddrList = make([]*Net_addr, listLen)
for i := 0; i < int(listLen); i++ {
a.AddrList[i] = &Net_addr{}
a.AddrList[i].DecodePayload(br)
if br.Err != nil {
return br.Err
}
}
return br.Err
}
// Implements messager interface
func (v *AddrMessage) EncodePayload(w io.Writer) error {
bw := &util.BinWriter{W: w}
listLen := uint64(len(v.AddrList))
bw.VarUint(listLen)
for _, addr := range v.AddrList {
addr.EncodePayload(bw)
}
return bw.Err
}
// Implements messager interface
func (v *AddrMessage) Command() command.Type {
return command.Addr
}

View file

@ -0,0 +1,40 @@
package payload
import (
"bytes"
"net"
"testing"
"time"
"github.com/CityOfZion/neo-go/pkg/wire/util/Checksum"
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
"github.com/stretchr/testify/assert"
)
func TestAddrMessageEncodeDecode(t *testing.T) {
ip := []byte(net.ParseIP("127.0.0.1").To16())
var ipByte [16]byte
copy(ipByte[:], ip)
netaddr, err := NewNetAddr(uint32(time.Now().Unix()), ipByte, 8080, protocol.NodePeerService)
addrmsg, err := NewAddrMessage()
addrmsg.AddNetAddr(netaddr)
buf := new(bytes.Buffer)
err = addrmsg.EncodePayload(buf)
expected := checksum.FromBuf(buf)
addrmsgDec, err := NewAddrMessage()
r := bytes.NewReader(buf.Bytes())
err = addrmsgDec.DecodePayload(r)
buf = new(bytes.Buffer)
err = addrmsgDec.EncodePayload(buf)
have := checksum.FromBuf(buf)
assert.Equal(t, nil, err)
assert.Equal(t, expected, have)
}

View file

@ -0,0 +1,36 @@
package payload
import (
"io"
"github.com/CityOfZion/neo-go/pkg/wire/util"
"github.com/CityOfZion/neo-go/pkg/wire/command"
)
type BlockMessage struct {
Block
}
func NewBlockMessage() (*BlockMessage, error) {
return &BlockMessage{}, nil
}
// Implements Messager interface
func (b *BlockMessage) DecodePayload(r io.Reader) error {
br := &util.BinReader{R: r}
b.Block.DecodePayload(br)
return br.Err
}
// Implements messager interface
func (b *BlockMessage) EncodePayload(w io.Writer) error {
bw := &util.BinWriter{W: w}
b.Block.EncodePayload(bw)
return bw.Err
}
// Implements messager interface
func (v *BlockMessage) Command() command.Type {
return command.Block
}

View file

@ -0,0 +1,29 @@
package payload
import (
"io"
"github.com/CityOfZion/neo-go/pkg/wire/command"
)
// No payload
type GetAddrMessage struct{}
func NewGetAddrMessage() (*GetAddrMessage, error) {
return &GetAddrMessage{}, nil
}
// Implements Messager interface
func (v *GetAddrMessage) DecodePayload(r io.Reader) error {
return nil
}
// Implements messager interface
func (v *GetAddrMessage) EncodePayload(w io.Writer) error {
return nil
}
// Implements messager interface
func (v *GetAddrMessage) Command() command.Type {
return command.GetAddr
}

View file

@ -0,0 +1,24 @@
package payload
import (
"bytes"
"testing"
"github.com/CityOfZion/neo-go/pkg/wire/util/Checksum"
"github.com/CityOfZion/neo-go/pkg/wire/command"
"github.com/stretchr/testify/assert"
)
func TestNewGetAddr(t *testing.T) {
getAddrMessage, err := NewGetAddrMessage()
assert.Equal(t, nil, err)
assert.Equal(t, command.GetAddr, getAddrMessage.Command())
buf := new(bytes.Buffer)
assert.Equal(t, int(3806393949), int(checksum.FromBuf(buf)))
assert.Equal(t, int(0), len(buf.Bytes()))
}

View file

@ -0,0 +1,22 @@
package payload
import (
"github.com/CityOfZion/neo-go/pkg/wire/command"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
type GetBlocksMessage struct {
*GetHeadersMessage
}
func NewGetBlocksMessage(start []util.Uint256, stop util.Uint256) (*GetBlocksMessage, error) {
GetHeaders, err := newAbstractGetHeaders(start, stop, command.GetBlocks)
if err != nil {
return nil, err
}
return &GetBlocksMessage{
GetHeaders,
}, nil
}

View file

@ -0,0 +1,27 @@
package payload
import (
"crypto/sha256"
"testing"
"github.com/CityOfZion/neo-go/pkg/wire/command"
"github.com/CityOfZion/neo-go/pkg/wire/util"
"github.com/stretchr/testify/assert"
)
func TestGetBlocksCommandType(t *testing.T) {
var (
start = []util.Uint256{
sha256.Sum256([]byte("a")),
sha256.Sum256([]byte("b")),
sha256.Sum256([]byte("c")),
sha256.Sum256([]byte("d")),
}
stop = sha256.Sum256([]byte("e"))
)
getBlocks, err := NewGetBlocksMessage(start, stop)
assert.Equal(t, err, nil)
assert.Equal(t, command.GetBlocks, getBlocks.Command())
}

View file

@ -0,0 +1,16 @@
package payload
import (
"github.com/CityOfZion/neo-go/pkg/wire/command"
)
type GetDataMessage struct {
*InvMessage
}
func NewGetDataMessage(typ InvType) (*GetDataMessage, error) {
getData, err := newAbstractInv(typ, command.GetData)
return &GetDataMessage{
getData,
}, err
}

View file

@ -0,0 +1,19 @@
package payload
import (
"testing"
"github.com/CityOfZion/neo-go/pkg/wire/command"
"github.com/stretchr/testify/assert"
)
func TestGetDataCommandType(t *testing.T) {
getData, err := NewGetDataMessage(InvTypeBlock)
assert.Equal(t, err, nil)
assert.Equal(t, command.GetData, getData.Command())
}
func TestOtherFunctions(t *testing.T) {
// Other capabilities are tested in the inherited struct
}

View file

@ -0,0 +1,59 @@
package payload
import (
"io"
"github.com/CityOfZion/neo-go/pkg/wire/command"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
type GetHeadersMessage struct {
cmd command.Type
hashStart []util.Uint256
hashStop util.Uint256
}
// Start contains the list of all headers you want to fetch
// End contains the list of the highest header hash you would like to fetch
func NewGetHeadersMessage(start []util.Uint256, stop util.Uint256) (*GetHeadersMessage, error) {
getHeaders := &GetHeadersMessage{command.GetHeaders, start, stop}
return getHeaders, nil
}
func newAbstractGetHeaders(start []util.Uint256, stop util.Uint256, cmd command.Type) (*GetHeadersMessage, error) {
getHeaders, err := NewGetHeadersMessage(start, stop)
if err != nil {
return nil, err
}
getHeaders.cmd = cmd
return getHeaders, nil
}
// Implements Messager interface
func (v *GetHeadersMessage) DecodePayload(r io.Reader) error {
br := util.BinReader{R: r}
lenStart := br.VarUint()
v.hashStart = make([]util.Uint256, lenStart)
br.Read(&v.hashStart)
br.Read(&v.hashStop)
return br.Err
}
// Implements messager interface
func (v *GetHeadersMessage) EncodePayload(w io.Writer) error {
bw := &util.BinWriter{W: w}
bw.VarUint(uint64(len(v.hashStart)))
bw.Write(v.hashStart)
bw.Write(v.hashStop)
return bw.Err
}
// Implements messager interface
func (v *GetHeadersMessage) Command() command.Type {
return v.cmd
}

View file

@ -0,0 +1,47 @@
package payload
import (
"bytes"
"crypto/sha256"
"testing"
"github.com/CityOfZion/neo-go/pkg/wire/util/Checksum"
"github.com/CityOfZion/neo-go/pkg/wire/util"
"github.com/stretchr/testify/assert"
)
// Test taken from neo-go v1
func TestGetHeadersEncodeDecode(t *testing.T) {
var (
start = []util.Uint256{
sha256.Sum256([]byte("a")),
sha256.Sum256([]byte("b")),
sha256.Sum256([]byte("c")),
sha256.Sum256([]byte("d")),
}
stop = sha256.Sum256([]byte("e"))
)
msgGetHeaders, err := NewGetHeadersMessage(start, stop)
assert.Equal(t, nil, err)
buf := new(bytes.Buffer)
err = msgGetHeaders.EncodePayload(buf)
assert.Equal(t, nil, err)
expected := checksum.FromBuf(buf)
msgGetHeadersDec, err := NewGetHeadersMessage([]util.Uint256{}, util.Uint256{})
assert.Equal(t, nil, err)
r := bytes.NewReader(buf.Bytes())
err = msgGetHeadersDec.DecodePayload(r)
assert.Equal(t, nil, err)
buf = new(bytes.Buffer)
err = msgGetHeadersDec.EncodePayload(buf)
have := checksum.FromBuf(buf)
assert.Equal(t, expected, have)
}

View file

@ -0,0 +1,78 @@
package payload
import (
"errors"
"io"
"github.com/CityOfZion/neo-go/pkg/wire/command"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
type HeadersMessage struct {
Headers []*BlockBase
// Padding that is fixed to 0
_ uint8
}
// Users can at most request 2k header
const (
maxHeadersAllowed = 2000
)
var (
ErrMaxHeaders = errors.New("Maximum amount of headers allowed is 2000")
)
func NewHeadersMessage() (*HeadersMessage, error) {
headers := &HeadersMessage{nil, 0}
return headers, nil
}
func (h *HeadersMessage) AddHeader(head *BlockBase) error {
if len(h.Headers)+1 > maxHeadersAllowed {
return ErrMaxHeaders
}
h.Headers = append(h.Headers, head)
return nil
}
// Implements Messager interface
func (v *HeadersMessage) DecodePayload(r io.Reader) error {
br := &util.BinReader{R: r}
lenHeaders := br.VarUint()
v.Headers = make([]*BlockBase, lenHeaders)
for i := 0; i < int(lenHeaders); i++ {
header := &BlockBase{}
header.DecodePayload(br)
var padding uint8
br.Read(&padding)
if padding != 0 {
return ErrPadding
}
v.Headers[i] = header
}
return br.Err
}
// Implements messager interface
func (v *HeadersMessage) EncodePayload(w io.Writer) error {
bw := &util.BinWriter{W: w}
bw.VarUint(uint64(len(v.Headers)))
for _, header := range v.Headers {
header.EncodePayload(bw)
bw.Write(uint8(0))
}
return bw.Err
}
// Implements messager interface
func (v *HeadersMessage) Command() command.Type {
return command.Headers
}

View file

@ -0,0 +1,82 @@
package payload
import (
"bytes"
"encoding/hex"
"testing"
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
"github.com/CityOfZion/neo-go/pkg/wire/util"
"github.com/CityOfZion/neo-go/pkg/wire/util/address"
"github.com/stretchr/testify/assert"
)
func TestNewHeaderMessage(t *testing.T) {
msgHeaders, err := NewHeadersMessage()
assert.Equal(t, nil, err)
assert.Equal(t, 0, len(msgHeaders.Headers))
}
func TestAddAndEncodeHeaders(t *testing.T) {
// uses block10 from mainnet
msgHeaders, _ := NewHeadersMessage()
prevH, _ := util.Uint256DecodeString("005fb74a6de169ce5daf59a114405e5b27238b2489690e3b2a60c14ddfc3b326")
merkleRoot, _ := util.Uint256DecodeString("ca6d58bcb837472c2f77877e68495b83fd5b714dfe0c8230a525f4511a3239f4")
invocationScript, _ := hex.DecodeString("4036fdd23248880c1c311bcd97df04fe6d740dc1bf340c26915f0466e31e81c039012eca7a760270389e04b58b99820fe49cf8c24c9afc65d696b4d3f406a1e6b5405172a9b461e68dd399c8716de11d31f7dd2ec3be327c636b024562db6ac5df1cffdbee74c994736fd49803234d2baffbc0054f28ba5ec76494a467b4106955bb4084af7746d269241628c667003e9d39288b190ad5cef218ada625cbba8be411bb153828d8d3634e8f586638e2448425bc5b671be69800392ccbdebc945a5099c7406f6a11824105ecad345e525957053e77fbc0119d6b3fa7f854527e816cfce0d95dac66888e07e8990c95103d8e46124aac16f152e088520d7ec8325e3a2456f840e5b77ef0e3c410b347ccaf8a87516d10b88d436563c80712153273993afc320ec49b638225f58de464a1345e62a564b398939f96f6f4b7cf21b583609f85495a")
verificationScript, _ := hex.DecodeString("552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae")
nextCon, _ := util.Uint160DecodeString(address.ToScriptHash("APyEx5f4Zm4oCHwFWiSTaph1fPBxZacYVR"))
msgHeaders.AddHeader(&BlockBase{
Version: 0,
Index: 10,
PrevHash: prevH.Reverse(),
MerkleRoot: merkleRoot.Reverse(),
Timestamp: 1476647551,
ConsensusData: 0xc0f0280216ff14bf,
NextConsensus: nextCon,
Witness: transaction.Witness{
InvocationScript: invocationScript,
VerificationScript: verificationScript,
},
})
assert.Equal(t, 1, len(msgHeaders.Headers))
err := msgHeaders.Headers[0].createHash()
assert.Equal(t, nil, err)
// Hash being correct, automatically verifies that the fields are encoded properly
assert.Equal(t, "f3c4ec44c07eccbda974f1ee34bc6654ab6d3f22cd89c2e5c593a16d6cc7e6e8", msgHeaders.Headers[0].Hash.String())
}
func TestEncodeDecode(t *testing.T) {
rawBlockHeaders := "010000000026b3c3df4dc1602a3b0e6989248b23275b5e4014a159af5dce69e16d4ab75f00f439321a51f425a530820cfe4d715bfd835b49687e87772f2c4737b8bc586dca7fda03580a000000bf14ff160228f0c059e75d652b5d3827bf04c165bbe9ef95cca4bf5501fd45014036fdd23248880c1c311bcd97df04fe6d740dc1bf340c26915f0466e31e81c039012eca7a760270389e04b58b99820fe49cf8c24c9afc65d696b4d3f406a1e6b5405172a9b461e68dd399c8716de11d31f7dd2ec3be327c636b024562db6ac5df1cffdbee74c994736fd49803234d2baffbc0054f28ba5ec76494a467b4106955bb4084af7746d269241628c667003e9d39288b190ad5cef218ada625cbba8be411bb153828d8d3634e8f586638e2448425bc5b671be69800392ccbdebc945a5099c7406f6a11824105ecad345e525957053e77fbc0119d6b3fa7f854527e816cfce0d95dac66888e07e8990c95103d8e46124aac16f152e088520d7ec8325e3a2456f840e5b77ef0e3c410b347ccaf8a87516d10b88d436563c80712153273993afc320ec49b638225f58de464a1345e62a564b398939f96f6f4b7cf21b583609f85495af1552102486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a7021024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d2102aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e2103b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c2103b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a2102ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba5542102df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e89509357ae00"
var headerMsg HeadersMessage
rawBlockBytes, _ := hex.DecodeString(rawBlockHeaders)
r := bytes.NewReader(rawBlockBytes)
err := headerMsg.DecodePayload(r)
assert.Equal(t, 1, len(headerMsg.Headers))
header := headerMsg.Headers[0]
err = header.createHash()
assert.Equal(t, "f3c4ec44c07eccbda974f1ee34bc6654ab6d3f22cd89c2e5c593a16d6cc7e6e8", header.Hash.String())
buf := new(bytes.Buffer)
err = headerMsg.EncodePayload(buf)
assert.Equal(t, nil, err)
assert.Equal(t, hex.EncodeToString(rawBlockBytes), hex.EncodeToString(buf.Bytes()))
}

View file

@ -0,0 +1,110 @@
package payload
import (
"errors"
"io"
"github.com/CityOfZion/neo-go/pkg/wire/command"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
type InvType uint8
//Inventory types
const (
InvTypeTx InvType = 0x01
InvTypeBlock InvType = 0x02
InvTypeConsensus InvType = 0xe0
)
const (
maxHashes = 0x10000000
)
var (
MaxHashError = errors.New("Max size For Hashes reached")
)
type InvMessage struct {
cmd command.Type
Type InvType
Hashes []util.Uint256
}
func NewInvMessage(typ InvType) (*InvMessage, error) {
inv := &InvMessage{
command.Inv,
typ,
nil,
}
return inv, nil
}
func newAbstractInv(typ InvType, cmd command.Type) (*InvMessage, error) {
inv, err := NewInvMessage(typ)
if err != nil {
return nil, err
}
inv.cmd = cmd
return inv, nil
}
func (i *InvMessage) AddHash(h util.Uint256) error {
if len(i.Hashes)+1 > maxHashes {
return MaxHashError
}
i.Hashes = append(i.Hashes, h)
return nil
}
func (i *InvMessage) AddHashes(hashes []util.Uint256) error {
var err error
for _, hash := range hashes {
err = i.AddHash(hash)
if err != nil {
break
}
}
return err
}
// Implements Messager interface
func (v *InvMessage) DecodePayload(r io.Reader) error {
br := &util.BinReader{R: r}
br.Read(&v.Type)
listLen := br.VarUint()
v.Hashes = make([]util.Uint256, listLen)
for i := 0; i < int(listLen); i++ {
br.Read(&v.Hashes[i])
}
return nil
}
// Implements messager interface
func (v *InvMessage) EncodePayload(w io.Writer) error {
bw := &util.BinWriter{W: w}
bw.Write(v.Type)
lenhashes := len(v.Hashes)
bw.VarUint(uint64(lenhashes))
for _, hash := range v.Hashes {
bw.Write(hash)
}
return bw.Err
}
// Implements messager interface
func (v *InvMessage) Command() command.Type {
return v.cmd
}

View file

@ -0,0 +1,78 @@
package payload
import (
"bytes"
"encoding/hex"
"testing"
"github.com/CityOfZion/neo-go/pkg/wire/command"
"github.com/CityOfZion/neo-go/pkg/wire/util"
"github.com/stretchr/testify/assert"
)
func TestNewInventory(t *testing.T) {
msgInv, err := NewInvMessage(InvTypeBlock)
assert.Equal(t, nil, err)
assert.Equal(t, command.Inv, msgInv.Command())
hash, _ := util.Uint256DecodeBytes([]byte("hello"))
err = msgInv.AddHash(hash)
assert.Equal(t, nil, err)
}
// Adjust test time or it will timeout
// func TestMaxHashes(t *testing.T) {
// msgInv, err := NewInvMessage(InvTypeBlock)
// assert.Equal(t, nil, err)
// hash, _ := util.Uint256DecodeBytes([]byte("hello"))
// for i := 0; i <= maxHashes+1; i++ {
// err = msgInv.AddHash(hash)
// }
// if err == nil {
// assert.Fail(t, "Max Hashes Exceeded, only allowed %v but have %v", maxHashes, len(msgInv.Hashes))
// } else if err != MaxHashError {
// assert.Fail(t, "Expected a MaxHashError, however we got %s", err.Error())
// }
// }
func TestEncodeDecodePayload(t *testing.T) {
msgInv, err := NewInvMessage(InvTypeBlock)
assert.Equal(t, nil, err)
blockOneHash := "d782db8a38b0eea0d7394e0f007c61c71798867578c77c387c08113903946cc9"
hash, _ := util.Uint256DecodeString(blockOneHash)
err = msgInv.AddHash(hash)
assert.Equal(t, nil, err)
buf := new(bytes.Buffer)
err = msgInv.EncodePayload(buf)
assert.Equal(t, nil, err)
numOfHashes := []byte{1}
expected := append([]byte{uint8(InvTypeBlock)}, numOfHashes...)
expected = append(expected, hash.Bytes()...)
assert.Equal(t, hex.EncodeToString(expected), hex.EncodeToString(buf.Bytes()))
var InvDec InvMessage
r := bytes.NewReader(buf.Bytes())
err = InvDec.DecodePayload(r)
assert.Equal(t, nil, err)
assert.Equal(t, 1, len(InvDec.Hashes))
assert.Equal(t, blockOneHash, hex.EncodeToString(InvDec.Hashes[0].Bytes()))
}
func TestEmptyInv(t *testing.T) {
msgInv, err := NewInvMessage(InvTypeBlock)
assert.Equal(t, nil, err)
buf := new(bytes.Buffer)
msgInv.EncodePayload(buf)
assert.Equal(t, []byte{byte(InvTypeBlock), 0}, buf.Bytes())
assert.Equal(t, 0, len(msgInv.Hashes))
}

View file

@ -0,0 +1,29 @@
package payload
import (
"io"
"github.com/CityOfZion/neo-go/pkg/wire/command"
)
// No payload
type GetMempool struct{}
func NewGetMempool() (*GetMempool, error) {
return &GetMempool{}, nil
}
// Implements Messager interface
func (v *GetMempool) DecodePayload(r io.Reader) error {
return nil
}
// Implements messager interface
func (v *GetMempool) EncodePayload(w io.Writer) error {
return nil
}
// Implements messager interface
func (v *GetMempool) Command() command.Type {
return command.Mempool
}

34
pkg/wire/payload/mtx.go Normal file
View file

@ -0,0 +1,34 @@
package payload
import (
"io"
"github.com/CityOfZion/neo-go/pkg/wire/command"
"github.com/CityOfZion/neo-go/pkg/wire/payload/transaction"
)
type TXMessage struct {
// w *bytes.Buffer
Tx transaction.Transactioner
}
func NewTXMessage(tx transaction.Transactioner) (*TXMessage, error) {
Tx := &TXMessage{tx}
return Tx, nil
}
// Implements Messager interface
func (t *TXMessage) DecodePayload(r io.Reader) error {
return t.Tx.Decode(r)
}
// Implements messager interface
func (t *TXMessage) EncodePayload(w io.Writer) error {
return t.Tx.Encode(w)
}
// Implements messager interface
func (v *TXMessage) Command() command.Type {
return command.TX
}

View file

@ -0,0 +1,29 @@
package payload
import (
"io"
"github.com/CityOfZion/neo-go/pkg/wire/command"
)
// No payload
type VerackMessage struct{}
func NewVerackMessage() (*VerackMessage, error) {
return &VerackMessage{}, nil
}
// Implements Messager interface
func (v *VerackMessage) DecodePayload(r io.Reader) error {
return nil
}
// Implements messager interface
func (v *VerackMessage) EncodePayload(w io.Writer) error {
return nil
}
// Implements messager interface
func (v *VerackMessage) Command() command.Type {
return command.Verack
}

View file

@ -0,0 +1,17 @@
package payload
import (
"testing"
"github.com/CityOfZion/neo-go/pkg/wire/command"
"github.com/stretchr/testify/assert"
)
func TestNewVerack(t *testing.T) {
verackMessage, err := NewVerackMessage()
assert.Equal(t, nil, err)
assert.Equal(t, command.Verack, verackMessage.Command())
}

View file

@ -0,0 +1,97 @@
// Copied and Modified for NEO from: https://github.com/decred/dcrd/blob/master/wire/VersionMessage.go
package payload
import (
"errors"
"io"
"net"
"time"
"github.com/CityOfZion/neo-go/pkg/wire/command"
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
const (
minMsgVersionSize = 28
)
// TODO: Refactor to pull out the useragent out of initialiser
// and have a seperate method to add it
type VersionMessage struct {
// w *bytes.Buffer
Version protocol.Version
Timestamp uint32
Services protocol.ServiceFlag
IP net.IP
Port uint16
Nonce uint32
UserAgent []byte
StartHeight uint32
Relay bool
}
var ErrInvalidNetAddr = errors.New("provided net.Addr is not a net.TCPAddr")
func NewVersionMessage(addr net.Addr, startHeight uint32, relay bool, pver protocol.Version, userAgent string, nonce uint32, services protocol.ServiceFlag) (*VersionMessage, error) {
tcpAddr, ok := addr.(*net.TCPAddr)
if !ok {
return nil, ErrInvalidNetAddr
}
version := &VersionMessage{
pver,
uint32(time.Now().Unix()),
services,
tcpAddr.IP,
uint16(tcpAddr.Port),
nonce,
[]byte(userAgent),
startHeight,
relay,
}
return version, nil
}
// Implements Messager interface
func (v *VersionMessage) DecodePayload(r io.Reader) error {
br := &util.BinReader{R: r}
br.Read(&v.Version)
br.Read(&v.Services)
br.Read(&v.Timestamp)
br.Read(&v.Port) // Port is not BigEndian
br.Read(&v.Nonce)
var lenUA uint8
br.Read(&lenUA)
v.UserAgent = make([]byte, lenUA)
br.Read(&v.UserAgent)
br.Read(&v.StartHeight)
br.Read(&v.Relay)
return br.Err
}
// Implements messager interface
func (v *VersionMessage) EncodePayload(w io.Writer) error {
bw := &util.BinWriter{W: w}
bw.Write(v.Version)
bw.Write(v.Services)
bw.Write(v.Timestamp)
bw.Write(v.Port) // Not big End
bw.Write(v.Nonce)
bw.Write(uint8(len(v.UserAgent)))
bw.Write(v.UserAgent)
bw.Write(v.StartHeight)
bw.Write(v.Relay)
return bw.Err
}
// Implements messager interface
func (v *VersionMessage) Command() command.Type {
return command.Version
}

View file

@ -0,0 +1,59 @@
package payload
import (
"bytes"
"math/rand"
"net"
"testing"
"time"
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
"github.com/stretchr/testify/assert"
)
func TestValidNewVersionMessage(t *testing.T) {
expectedIP := "127.0.0.1"
expectedPort := 8333
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort}
nonce := randRange(12949672, 42949672)
message, err := NewVersionMessage(tcpAddrMe, 0, true, protocol.DefaultVersion, protocol.UserAgent, nonce, protocol.NodePeerService)
assert.Equal(t, nil, err)
assert.Equal(t, expectedIP, message.IP.String())
assert.Equal(t, uint16(expectedPort), message.Port)
assert.Equal(t, protocol.DefaultVersion, message.Version)
}
func TestEncode(t *testing.T) {
expectedIP := "127.0.0.1"
expectedPort := 8333
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort}
nonce := randRange(12949672, 42949672)
message, err := NewVersionMessage(tcpAddrMe, 0, true, protocol.DefaultVersion, protocol.UserAgent, nonce, protocol.NodePeerService)
buf := new(bytes.Buffer)
err = message.EncodePayload(buf)
assert.Equal(t, nil, err)
assert.Equal(t, len(message.UserAgent)+minMsgVersionSize, int(buf.Len()))
}
func TestLenIsCorrect(t *testing.T) {
expectedIP := "127.0.0.1"
expectedPort := 8333
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP(expectedIP), Port: expectedPort}
nonce := randRange(12949672, 42949672)
message, err := NewVersionMessage(tcpAddrMe, 0, true, protocol.DefaultVersion, protocol.UserAgent, nonce, protocol.NodePeerService)
buf := new(bytes.Buffer)
err = message.EncodePayload(buf)
assert.Equal(t, nil, err)
assert.Equal(t, len(message.UserAgent)+minMsgVersionSize, len(buf.Bytes()))
}
func randRange(min, max int) uint32 {
rand.Seed(time.Now().Unix() + int64(rand.Uint64()))
return uint32(rand.Intn(max-min) + min)
}

View file

@ -0,0 +1,54 @@
package payload
import (
"net"
"strconv"
"time"
"github.com/CityOfZion/neo-go/pkg/wire/protocol"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
// Once a VersionMessage is received, we can then store it inside of AddrMessage struct
// TODO: store this inside version message and have a bool to indicate whether to encode ip
// VersionMessage does not encodeIP
type Net_addr struct {
Timestamp uint32
IP [16]byte
Port uint16
Service protocol.ServiceFlag
}
func NewNetAddr(time uint32, ip [16]byte, port uint16, service protocol.ServiceFlag) (*Net_addr, error) {
return &Net_addr{time, ip, port, service}, nil
}
func NewAddrFromVersionMessage(version VersionMessage) (*Net_addr, error) {
var ip [16]byte
copy(ip[:], []byte(version.IP)[:16])
return NewNetAddr(version.Timestamp, ip, version.Port, version.Services)
}
func (n *Net_addr) EncodePayload(bw *util.BinWriter) {
bw.Write(uint32(time.Now().Unix()))
bw.Write(protocol.NodePeerService)
bw.WriteBigEnd(n.IP)
bw.WriteBigEnd(n.Port)
}
func (n *Net_addr) DecodePayload(br *util.BinReader) {
br.Read(&n.Timestamp)
br.Read(&n.Service)
br.ReadBigEnd(&n.IP)
br.ReadBigEnd(&n.Port)
}
func (n *Net_addr) IPPort() string {
ip := net.IP(n.IP[:]).String()
port := strconv.Itoa(int(n.Port))
ipport := ip + ":" + port
return ipport
}

View file

@ -0,0 +1,65 @@
package transaction
import (
"errors"
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
// Attribute represents a Transaction attribute.
type Attribute struct {
Usage AttrUsage
Data []byte
}
var (
ErrMaxData = errors.New("Max Size of Attribute reached")
)
const (
maxAttrSize = 65535
)
func (a *Attribute) Encode(bw *util.BinWriter) {
if len(a.Data) > maxAttrSize {
bw.Err = ErrMaxData
return
}
bw.Write(uint8(a.Usage))
if a.Usage == DescriptionURL || a.Usage == Vote || (a.Usage >= Hash1 && a.Usage <= Hash15) {
bw.Write(a.Data[:32])
} else if a.Usage == Script {
bw.Write(a.Data[:20])
} else if a.Usage == ECDH02 || a.Usage == ECDH03 {
bw.Write(a.Data[1:33])
} else if a.Usage == CertURL || a.Usage == DescriptionURL || a.Usage == Description || a.Usage >= Remark {
bw.VarUint(uint64(len(a.Data)))
bw.Write(a.Data)
} else {
bw.Write(a.Data)
}
}
func (a *Attribute) Decode(br *util.BinReader) {
br.Read(&a.Usage)
if a.Usage == DescriptionURL || a.Usage == Vote || a.Usage >= Hash1 && a.Usage <= Hash15 {
a.Data = make([]byte, 32)
br.Read(&a.Data)
} else if a.Usage == Script {
a.Data = make([]byte, 20)
br.Read(&a.Data)
} else if a.Usage == ECDH02 || a.Usage == ECDH03 {
a.Data = make([]byte, 32)
br.Read(&a.Data)
} else if a.Usage == CertURL || a.Usage == DescriptionURL || a.Usage == Description || a.Usage >= Remark {
lenData := br.VarUint()
a.Data = make([]byte, lenData)
br.Read(&a.Data)
} else {
br.Read(&a.Data)
}
}

View file

@ -0,0 +1,28 @@
package transaction
import "github.com/CityOfZion/neo-go/pkg/wire/util"
// Input represents a Transaction input.
type Input struct {
// The hash of the previous transaction.
PrevHash util.Uint256
// The index of the previous transaction.
PrevIndex uint16
}
func NewInput(prevHash util.Uint256, prevIndex uint16) *Input {
return &Input{
prevHash,
prevIndex,
}
}
func (i *Input) Encode(bw *util.BinWriter) {
bw.Write(i.PrevHash)
bw.Write(i.PrevIndex)
}
func (i *Input) Decode(br *util.BinReader) {
br.Read(&i.PrevHash)
br.Read(&i.PrevIndex)
}

View file

@ -0,0 +1,34 @@
package transaction
import "github.com/CityOfZion/neo-go/pkg/wire/util"
type Output struct {
// The NEO asset id used in the transaction.
AssetID util.Uint256
// Amount of AssetType send or received.
Amount int64
// The address of the remittee.
ScriptHash util.Uint160
}
func NewOutput(assetID util.Uint256, Amount int64, ScriptHash util.Uint160) *Output {
return &Output{
assetID,
Amount,
ScriptHash,
}
}
func (o *Output) Encode(bw *util.BinWriter) {
bw.Write(o.AssetID)
bw.Write(o.Amount)
bw.Write(o.ScriptHash)
}
func (o *Output) Decode(br *util.BinReader) {
br.Read(&o.AssetID)
br.Read(&o.Amount)
br.Read(&o.ScriptHash)
}

View file

@ -0,0 +1,34 @@
package transaction
import (
"github.com/CityOfZion/neo-go/pkg/wire/util"
)
type Witness struct {
InvocationScript []byte
VerificationScript []byte
}
func (s *Witness) Encode(bw *util.BinWriter) error {
bw.VarUint(uint64(len(s.InvocationScript)))
bw.Write(s.InvocationScript)
bw.VarUint(uint64(len(s.VerificationScript)))
bw.Write(s.VerificationScript)
return bw.Err
}
func (s *Witness) Decode(br *util.BinReader) error {
lenb := br.VarUint()
s.InvocationScript = make([]byte, lenb)
br.Read(s.InvocationScript)
lenb = br.VarUint()
s.VerificationScript = make([]byte, lenb)
br.Read(s.VerificationScript)
return br.Err
}

View file

@ -0,0 +1,16 @@
package transaction
// AssetType represent a NEO asset type
type AssetType uint8
// Valid asset types.
const (
CreditFlag AssetType = 0x40
DutyFlag AssetType = 0x80
GoverningToken AssetType = 0x00
UtilityToken AssetType = 0x01
Currency AssetType = 0x08
Share AssetType = DutyFlag | 0x10
Invoice AssetType = DutyFlag | 0x18
Token AssetType = CreditFlag | 0x20
)

Some files were not shown because too many files have changed in this diff Show more