@ -0,0 +1,39 @@ | |||
// +build libsecp256k1 | |||
package secp256k1 | |||
import ( | |||
"github.com/magiconair/properties/assert" | |||
"testing" | |||
"github.com/stretchr/testify/require" | |||
) | |||
func TestPrivKeySecp256k1SignVerify(t *testing.T) { | |||
msg := []byte("A.1.2 ECC Key Pair Generation by Testing Candidates") | |||
priv := GenPrivKey() | |||
tests := []struct { | |||
name string | |||
privKey PrivKeySecp256k1 | |||
wantSignErr bool | |||
wantVerifyPasses bool | |||
}{ | |||
{name: "valid sign-verify round", privKey: priv, wantSignErr: false, wantVerifyPasses: true}, | |||
{name: "invalid private key", privKey: [32]byte{}, wantSignErr: true, wantVerifyPasses: false}, | |||
} | |||
for _, tt := range tests { | |||
t.Run(tt.name, func(t *testing.T) { | |||
got, err := tt.privKey.Sign(msg) | |||
if tt.wantSignErr { | |||
require.Error(t, err) | |||
t.Logf("Got error: %s", err) | |||
return | |||
} | |||
require.NoError(t, err) | |||
require.NotNil(t, got) | |||
pub := tt.privKey.PubKey() | |||
assert.Equal(t, tt.wantVerifyPasses, pub.VerifyBytes(msg, got)) | |||
}) | |||
} | |||
} |
@ -0,0 +1,45 @@ | |||
package secp256k1 | |||
import ( | |||
"bytes" | |||
"math/big" | |||
"testing" | |||
"github.com/stretchr/testify/require" | |||
underlyingSecp256k1 "github.com/btcsuite/btcd/btcec" | |||
) | |||
func Test_genPrivKey(t *testing.T) { | |||
empty := make([]byte, 32) | |||
oneB := big.NewInt(1).Bytes() | |||
onePadded := make([]byte, 32) | |||
copy(onePadded[32-len(oneB):32], oneB) | |||
t.Logf("one padded: %v, len=%v", onePadded, len(onePadded)) | |||
validOne := append(empty, onePadded...) | |||
tests := []struct { | |||
name string | |||
notSoRand []byte | |||
shouldPanic bool | |||
}{ | |||
{"empty bytes (panics because 1st 32 bytes are zero and 0 is not a valid field element)", empty, true}, | |||
{"curve order: N", underlyingSecp256k1.S256().N.Bytes(), true}, | |||
{"valid because 0 < 1 < N", validOne, false}, | |||
} | |||
for _, tt := range tests { | |||
t.Run(tt.name, func(t *testing.T) { | |||
if tt.shouldPanic { | |||
require.Panics(t, func() { | |||
genPrivKey(bytes.NewReader(tt.notSoRand)) | |||
}) | |||
return | |||
} | |||
got := genPrivKey(bytes.NewReader(tt.notSoRand)) | |||
fe := new(big.Int).SetBytes(got[:]) | |||
require.True(t, fe.Cmp(underlyingSecp256k1.S256().N) < 0) | |||
require.True(t, fe.Sign() > 0) | |||
}) | |||
} | |||
} |
@ -0,0 +1,65 @@ | |||
# Release management scripts | |||
## Overview | |||
The scripts in this folder are used for release management in CircleCI. Although the scripts are fully configurable using input parameters, | |||
the default settings were modified to accommodate CircleCI execution. | |||
# Build scripts | |||
These scripts help during the build process. They prepare the release files. | |||
## bump-semver.py | |||
Bumps the semantic version of the input `--version`. Versions are expected in vMAJOR.MINOR.PATCH format or vMAJOR.MINOR format. | |||
In vMAJOR.MINOR format, the result will be patch version 0 of that version, for example `v1.2 -> v1.2.0`. | |||
In vMAJOR.MINOR.PATCH format, the result will be a bumped PATCH version, for example `v1.2.3 -> v1.2.4`. | |||
If the PATCH number contains letters, it is considered a development version, in which case, the result is the non-development version of that number. | |||
The patch number will not be bumped, only the "-dev" or similar additional text will be removed. For example: `v1.2.6-rc1 -> v1.2.6`. | |||
## zip-file.py | |||
Specialized ZIP command for release management. Special features: | |||
1. Uses Python ZIP libaries, so the `zip` command does not need to be installed. | |||
1. Can only zip one file. | |||
1. Optionally gets file version, Go OS and architecture. | |||
1. By default all inputs and output is formatted exactly how CircleCI needs it. | |||
By default, the command will try to ZIP the file at `build/tendermint_${GOOS}_${GOARCH}`. | |||
This can be changed with the `--file` input parameter. | |||
By default, the command will output the ZIP file to `build/tendermint_${CIRCLE_TAG}_${GOOS}_${GOARCH}.zip`. | |||
This can be changed with the `--destination` (folder), `--version`, `--goos` and `--goarch` input parameters respectively. | |||
## sha-files.py | |||
Specialized `shasum` command for release management. Special features: | |||
1. Reads all ZIP files in the given folder. | |||
1. By default all inputs and output is formatted exactly how CircleCI needs it. | |||
By default, the command will look up all ZIP files in the `build/` folder. | |||
By default, the command will output results into the `build/SHA256SUMS` file. | |||
# GitHub management | |||
Uploading build results to GitHub requires at least these steps: | |||
1. Create a new release on GitHub with content | |||
2. Upload all binaries to the release | |||
3. Publish the release | |||
The below scripts help with these steps. | |||
## github-draft.py | |||
Creates a GitHub release and fills the content with the CHANGELOG.md link. The version number can be changed by the `--version` parameter. | |||
By default, the command will use the tendermint/tendermint organization/repo, which can be changed using the `--org` and `--repo` parameters. | |||
By default, the command will get the version number from the `${CIRCLE_TAG}` variable. | |||
Returns the GitHub release ID. | |||
## github-upload.py | |||
Upload a file to a GitHub release. The release is defined by the mandatory `--id` (release ID) input parameter. | |||
By default, the command will upload the file `/tmp/workspace/tendermint_${CIRCLE_TAG}_${GOOS}_${GOARCH}.zip`. This can be changed by the `--file` input parameter. | |||
## github-publish.py | |||
Publish a GitHub release. The release is defined by the mandatory `--id` (release ID) input parameter. | |||
@ -0,0 +1,37 @@ | |||
#!/usr/bin/env python | |||
# Bump the release number of a semantic version number and print it. --version is required. | |||
# Version is | |||
# - vA.B.C, in which case vA.B.C+1 will be returned | |||
# - vA.B.C-devorwhatnot in which case vA.B.C will be returned | |||
# - vA.B in which case vA.B.0 will be returned | |||
import re | |||
import argparse | |||
def semver(ver): | |||
if re.match('v[0-9]+\.[0-9]+',ver) is None: | |||
ver="v0.0" | |||
#raise argparse.ArgumentTypeError('--version must be a semantic version number with major, minor and patch numbers') | |||
return ver | |||
if __name__ == "__main__": | |||
parser = argparse.ArgumentParser() | |||
parser.add_argument("--version", help="Version number to bump, e.g.: v1.0.0", required=True, type=semver) | |||
args = parser.parse_args() | |||
found = re.match('(v[0-9]+\.[0-9]+)(\.(.+))?', args.version) | |||
majorminorprefix = found.group(1) | |||
patch = found.group(3) | |||
if patch is None: | |||
patch = "0-new" | |||
if re.match('[0-9]+$',patch) is None: | |||
patchfound = re.match('([0-9]+)',patch) | |||
patch = int(patchfound.group(1)) | |||
else: | |||
patch = int(patch) + 1 | |||
print("{0}.{1}".format(majorminorprefix, patch)) |
@ -0,0 +1,61 @@ | |||
#!/usr/bin/env python | |||
# Create a draft release on GitHub. By default in the tendermint/tendermint repo. | |||
# Optimized for CircleCI | |||
import argparse | |||
import httplib | |||
import json | |||
import os | |||
from base64 import b64encode | |||
def request(org, repo, data): | |||
user_and_pass = b64encode(b"{0}:{1}".format(os.environ['GITHUB_USERNAME'], os.environ['GITHUB_TOKEN'])).decode("ascii") | |||
headers = { | |||
'User-Agent': 'tenderbot', | |||
'Accept': 'application/vnd.github.v3+json', | |||
'Authorization': 'Basic %s' % user_and_pass | |||
} | |||
conn = httplib.HTTPSConnection('api.github.com', timeout=5) | |||
conn.request('POST', '/repos/{0}/{1}/releases'.format(org,repo), data, headers) | |||
response = conn.getresponse() | |||
if response.status < 200 or response.status > 299: | |||
print("{0}: {1}".format(response.status, response.reason)) | |||
conn.close() | |||
raise IOError(response.reason) | |||
responsedata = response.read() | |||
conn.close() | |||
return json.loads(responsedata) | |||
def create_draft(org,repo,branch,version): | |||
draft = { | |||
'tag_name': version, | |||
'target_commitish': '{0}'.format(branch), | |||
'name': '{0} (WARNING: ALPHA SOFTWARE)'.format(version), | |||
'body': '<a href=https://github.com/{0}/{1}/blob/master/CHANGELOG.md#{2}>https://github.com/{0}/{1}/blob/master/CHANGELOG.md#{2}</a>'.format(org,repo,version.replace('v','').replace('.','')), | |||
'draft': True, | |||
'prerelease': False | |||
} | |||
data=json.dumps(draft) | |||
return request(org, repo, data) | |||
if __name__ == "__main__": | |||
parser = argparse.ArgumentParser() | |||
parser.add_argument("--org", default="tendermint", help="GitHub organization") | |||
parser.add_argument("--repo", default="tendermint", help="GitHub repository") | |||
parser.add_argument("--branch", default=os.environ.get('CIRCLE_BRANCH'), help="Branch to build from, e.g.: v1.0") | |||
parser.add_argument("--version", default=os.environ.get('CIRCLE_TAG'), help="Version number for binary, e.g.: v1.0.0") | |||
args = parser.parse_args() | |||
if not os.environ.has_key('GITHUB_USERNAME'): | |||
raise parser.error('environment variable GITHUB_USERNAME is required') | |||
if not os.environ.has_key('GITHUB_TOKEN'): | |||
raise parser.error('environment variable GITHUB_TOKEN is required') | |||
release = create_draft(args.org,args.repo,args.branch,args.version) | |||
print(release["id"]) | |||
@ -0,0 +1,52 @@ | |||
#!/usr/bin/env python | |||
# Open a PR against the develop branch. --branch required. | |||
# Optimized for CircleCI | |||
import json | |||
import os | |||
import argparse | |||
import httplib | |||
from base64 import b64encode | |||
def request(org, repo, data): | |||
user_and_pass = b64encode(b"{0}:{1}".format(os.environ['GITHUB_USERNAME'], os.environ['GITHUB_TOKEN'])).decode("ascii") | |||
headers = { | |||
'User-Agent': 'tenderbot', | |||
'Accept': 'application/vnd.github.v3+json', | |||
'Authorization': 'Basic %s' % user_and_pass | |||
} | |||
conn = httplib.HTTPSConnection('api.github.com', timeout=5) | |||
conn.request('POST', '/repos/{0}/{1}/pulls'.format(org,repo), data, headers) | |||
response = conn.getresponse() | |||
if response.status < 200 or response.status > 299: | |||
print(response) | |||
conn.close() | |||
raise IOError(response.reason) | |||
responsedata = response.read() | |||
conn.close() | |||
return json.loads(responsedata) | |||
if __name__ == "__main__": | |||
parser = argparse.ArgumentParser() | |||
parser.add_argument("--org", default="tendermint", help="GitHub organization. Defaults to tendermint.") | |||
parser.add_argument("--repo", default="tendermint", help="GitHub repository. Defaults to tendermint.") | |||
parser.add_argument("--head", help="The name of the branch where your changes are implemented.", required=True) | |||
parser.add_argument("--base", help="The name of the branch you want the changes pulled into.", required=True) | |||
parser.add_argument("--title", default="Security release {0}".format(os.environ.get('CIRCLE_TAG')), help="The title of the pull request.") | |||
args = parser.parse_args() | |||
if not os.environ.has_key('GITHUB_USERNAME'): | |||
raise parser.error('GITHUB_USERNAME not set.') | |||
if not os.environ.has_key('GITHUB_TOKEN'): | |||
raise parser.error('GITHUB_TOKEN not set.') | |||
if os.environ.get('CIRCLE_TAG') is None: | |||
raise parser.error('CIRCLE_TAG not set.') | |||
result = request(args.org, args.repo, data=json.dumps({'title':"{0}".format(args.title),'head':"{0}".format(args.head),'base':"{0}".format(args.base),'body':"<Please fill in details.>"})) | |||
print(result['html_url']) |
@ -0,0 +1,28 @@ | |||
#!/bin/sh | |||
# github-public-newbranch.bash - create public branch from the security repository | |||
set -euo pipefail | |||
# Create new branch | |||
BRANCH="${CIRCLE_TAG:-v0.0.0}-security-`date -u +%Y%m%d%H%M%S`" | |||
# Check if the patch release exist already as a branch | |||
if [ -n "`git branch | grep '${BRANCH}'`" ]; then | |||
echo "WARNING: Branch ${BRANCH} already exists." | |||
else | |||
echo "Creating branch ${BRANCH}." | |||
git branch "${BRANCH}" | |||
fi | |||
# ... and check it out | |||
git checkout "${BRANCH}" | |||
# Add entry to public repository | |||
git remote add tendermint-origin git@github.com:tendermint/tendermint.git | |||
# Push branch and tag to public repository | |||
git push tendermint-origin | |||
git push tendermint-origin --tags | |||
# Create a PR from the public branch to the assumed release branch in public (release branch has to exist) | |||
python -u scripts/release_management/github-openpr.py --head "${BRANCH}" --base "${BRANCH:%.*}" |
@ -0,0 +1,53 @@ | |||
#!/usr/bin/env python | |||
# Publish an existing GitHub draft release. --id required. | |||
# Optimized for CircleCI | |||
import json | |||
import os | |||
import argparse | |||
import httplib | |||
from base64 import b64encode | |||
def request(org, repo, id, data): | |||
user_and_pass = b64encode(b"{0}:{1}".format(os.environ['GITHUB_USERNAME'], os.environ['GITHUB_TOKEN'])).decode("ascii") | |||
headers = { | |||
'User-Agent': 'tenderbot', | |||
'Accept': 'application/vnd.github.v3+json', | |||
'Authorization': 'Basic %s' % user_and_pass | |||
} | |||
conn = httplib.HTTPSConnection('api.github.com', timeout=5) | |||
conn.request('POST', '/repos/{0}/{1}/releases/{2}'.format(org,repo,id), data, headers) | |||
response = conn.getresponse() | |||
if response.status < 200 or response.status > 299: | |||
print(response) | |||
conn.close() | |||
raise IOError(response.reason) | |||
responsedata = response.read() | |||
conn.close() | |||
return json.loads(responsedata) | |||
if __name__ == "__main__": | |||
parser = argparse.ArgumentParser() | |||
parser.add_argument("--org", default="tendermint", help="GitHub organization") | |||
parser.add_argument("--repo", default="tendermint", help="GitHub repository") | |||
parser.add_argument("--id", help="GitHub release ID", required=True, type=int) | |||
parser.add_argument("--version", default=os.environ.get('CIRCLE_TAG'), help="Version number for the release, e.g.: v1.0.0") | |||
args = parser.parse_args() | |||
if not os.environ.has_key('GITHUB_USERNAME'): | |||
raise parser.error('GITHUB_USERNAME not set.') | |||
if not os.environ.has_key('GITHUB_TOKEN'): | |||
raise parser.error('GITHUB_TOKEN not set.') | |||
try: | |||
result = request(args.org, args.repo, args.id, data=json.dumps({'draft':False,'tag_name':"{0}".format(args.version)})) | |||
except IOError as e: | |||
print(e) | |||
result = request(args.org, args.repo, args.id, data=json.dumps({'draft':False,'tag_name':"{0}-autorelease".format(args.version)})) | |||
print(result['name']) |
@ -0,0 +1,68 @@ | |||
#!/usr/bin/env python | |||
# Upload a file to a GitHub draft release. --id and --file are required. | |||
# Optimized for CircleCI | |||
import json | |||
import os | |||
import re | |||
import argparse | |||
import mimetypes | |||
import httplib | |||
from base64 import b64encode | |||
def request(baseurl, path, mimetype, mimeencoding, data): | |||
user_and_pass = b64encode(b"{0}:{1}".format(os.environ['GITHUB_USERNAME'], os.environ['GITHUB_TOKEN'])).decode("ascii") | |||
headers = { | |||
'User-Agent': 'tenderbot', | |||
'Accept': 'application/vnd.github.v3.raw+json', | |||
'Authorization': 'Basic %s' % user_and_pass, | |||
'Content-Type': mimetype, | |||
'Content-Encoding': mimeencoding | |||
} | |||
conn = httplib.HTTPSConnection(baseurl, timeout=5) | |||
conn.request('POST', path, data, headers) | |||
response = conn.getresponse() | |||
if response.status < 200 or response.status > 299: | |||
print(response) | |||
conn.close() | |||
raise IOError(response.reason) | |||
responsedata = response.read() | |||
conn.close() | |||
return json.loads(responsedata) | |||
if __name__ == "__main__": | |||
parser = argparse.ArgumentParser() | |||
parser.add_argument("--id", help="GitHub release ID", required=True, type=int) | |||
parser.add_argument("--file", default="/tmp/workspace/tendermint_{0}_{1}_{2}.zip".format(os.environ.get('CIRCLE_TAG'),os.environ.get('GOOS'),os.environ.get('GOARCH')), help="File to upload") | |||
parser.add_argument("--return-id-only", help="Return only the release ID after upload to GitHub.", action='store_true') | |||
args = parser.parse_args() | |||
if not os.environ.has_key('GITHUB_USERNAME'): | |||
raise parser.error('GITHUB_USERNAME not set.') | |||
if not os.environ.has_key('GITHUB_TOKEN'): | |||
raise parser.error('GITHUB_TOKEN not set.') | |||
mimetypes.init() | |||
filename = os.path.basename(args.file) | |||
mimetype,mimeencoding = mimetypes.guess_type(filename, strict=False) | |||
if mimetype is None: | |||
mimetype = 'application/zip' | |||
if mimeencoding is None: | |||
mimeencoding = 'utf8' | |||
with open(args.file,'rb') as f: | |||
asset = f.read() | |||
result = request('uploads.github.com', '/repos/tendermint/tendermint/releases/{0}/assets?name={1}'.format(args.id, filename), mimetype, mimeencoding, asset) | |||
if args.return_id_only: | |||
print(result['id']) | |||
else: | |||
print(result['browser_download_url']) | |||
@ -0,0 +1,35 @@ | |||
#!/usr/bin/env python | |||
# Create SHA256 summaries from all ZIP files in a folder | |||
# Optimized for CircleCI | |||
import re | |||
import os | |||
import argparse | |||
import zipfile | |||
import hashlib | |||
BLOCKSIZE = 65536 | |||
if __name__ == "__main__": | |||
parser = argparse.ArgumentParser() | |||
parser.add_argument("--folder", default="/tmp/workspace", help="Folder to look for, for ZIP files") | |||
parser.add_argument("--shafile", default="/tmp/workspace/SHA256SUMS", help="SHA256 summaries File") | |||
args = parser.parse_args() | |||
for filename in os.listdir(args.folder): | |||
if re.search('\.zip$',filename) is None: | |||
continue | |||
if not os.path.isfile(os.path.join(args.folder, filename)): | |||
continue | |||
with open(args.shafile,'a+') as shafile: | |||
hasher = hashlib.sha256() | |||
with open(os.path.join(args.folder, filename),'r') as f: | |||
buf = f.read(BLOCKSIZE) | |||
while len(buf) > 0: | |||
hasher.update(buf) | |||
buf = f.read(BLOCKSIZE) | |||
shafile.write("{0} {1}\n".format(hasher.hexdigest(),filename)) | |||
@ -0,0 +1,44 @@ | |||
#!/usr/bin/env python | |||
# ZIP one file as "tendermint" into a ZIP like tendermint_VERSION_OS_ARCH.zip | |||
# Use environment variables CIRCLE_TAG, GOOS and GOARCH for easy input parameters. | |||
# Optimized for CircleCI | |||
import os | |||
import argparse | |||
import zipfile | |||
import hashlib | |||
BLOCKSIZE = 65536 | |||
def zip_asset(file,destination,arcname,version,goos,goarch): | |||
filename = os.path.basename(file) | |||
output = "{0}/{1}_{2}_{3}_{4}.zip".format(destination,arcname,version,goos,goarch) | |||
with zipfile.ZipFile(output,'w') as f: | |||
f.write(filename=file,arcname=arcname) | |||
f.comment=filename | |||
return output | |||
if __name__ == "__main__": | |||
parser = argparse.ArgumentParser() | |||
parser.add_argument("--file", default="build/tendermint_{0}_{1}".format(os.environ.get('GOOS'),os.environ.get('GOARCH')), help="File to zip") | |||
parser.add_argument("--destination", default="build", help="Destination folder for files") | |||
parser.add_argument("--version", default=os.environ.get('CIRCLE_TAG'), help="Version number for binary, e.g.: v1.0.0") | |||
parser.add_argument("--goos", default=os.environ.get('GOOS'), help="GOOS parameter") | |||
parser.add_argument("--goarch", default=os.environ.get('GOARCH'), help="GOARCH parameter") | |||
args = parser.parse_args() | |||
if args.version is None: | |||
raise parser.error("argument --version is required") | |||
if args.goos is None: | |||
raise parser.error("argument --goos is required") | |||
if args.goarch is None: | |||
raise parser.error("argument --goarch is required") | |||
file = zip_asset(args.file,args.destination,"tendermint",args.version,args.goos,args.goarch) | |||
print(file) | |||