From 5ae1a0a9434058bc4bbc620a8dbca97007a93585 Mon Sep 17 00:00:00 2001 From: Rick Carlino Date: Sat, 21 Sep 2019 21:30:03 -0500 Subject: [PATCH] Coo... coo... --- .gitignore | 2 + .rspec | 1 + Gemfile | 12 +++++ Gemfile.lock | 44 +++++++++++++++ README.md | 35 ++++++++++++ TODO.md | 100 +++++++++++++++++++++++++++++++++++ dist/pigeon.rb | 6 +++ dist/pigeon/config.rb | 6 +++ dist/pigeon/key_pair.rb | 43 +++++++++++++++ dist/pigeon/storage.rb | 75 ++++++++++++++++++++++++++ pigeon-cli | 63 ++++++++++++++++++++++ spec/pigeon/config_spec.rb | 7 +++ spec/pigeon/key_pair_spec.rb | 19 +++++++ spec/spec_helper.rb | 9 ++++ 14 files changed, 422 insertions(+) create mode 100644 .gitignore create mode 100644 .rspec create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 README.md create mode 100644 TODO.md create mode 100644 dist/pigeon.rb create mode 100644 dist/pigeon/config.rb create mode 100644 dist/pigeon/key_pair.rb create mode 100644 dist/pigeon/storage.rb create mode 100755 pigeon-cli create mode 100644 spec/pigeon/config_spec.rb create mode 100644 spec/pigeon/key_pair_spec.rb create mode 100644 spec/spec_helper.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..83bda59 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +coverage/ +.pigeon/ diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..c99d2e7 --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..2c5ec10 --- /dev/null +++ b/Gemfile @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "ed25519" +gem "thor" + +group :dev do + gem "rspec" + gem "pry" + gem "simplecov" +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..b4c94af --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,44 @@ +GEM + remote: https://rubygems.org/ + specs: + coderay (1.1.2) + diff-lcs (1.3) + docile (1.3.2) + ed25519 (1.2.4) + json (2.2.0) + method_source (0.9.2) + pry (0.12.2) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + rspec (3.8.0) + rspec-core (~> 3.8.0) + rspec-expectations (~> 3.8.0) + rspec-mocks (~> 3.8.0) + rspec-core (3.8.2) + rspec-support (~> 3.8.0) + rspec-expectations (3.8.4) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-mocks (3.8.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-support (3.8.2) + simplecov (0.17.1) + docile (~> 1.1) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.2) + thor (0.20.3) + +PLATFORMS + ruby + +DEPENDENCIES + ed25519 + pry + rspec + simplecov + thor + +BUNDLED WITH + 2.0.2 diff --git a/README.md b/README.md new file mode 100644 index 0000000..f7ff465 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# Pigeon Ruby + +A WIP pigeon protocol client. + +# How to Use + +This is a pre-release skeleton project. There is no gem yet. The gem will be released after we are fully compliant with the spec and have high test coverage stats. + +To get started, clone this repo and run `./pigeon-cli` in place of `pigeon`. + +Eg: `pigeon identity show` becomes `./pigeon-cli show`. + +# Current Status + + - [X] pigeon status + - [X] pigeon identity new + - [X] pigeon identity show + + - [ ] pigeon blob set + - [ ] pigeon blob get + + - [ ] pigeon message new + - [ ] pigeon message current + - [ ] pigeon message append + - [ ] pigeon message save + - [ ] pigeon message find + - [ ] pigeon message find-all + + - [ ] pigeon peer add + - [ ] pigeon peer remove + - [ ] pigeon peer block + - [ ] pigeon peer all + + - [ ] pigeon bundle create + - [ ] pigeon bundle consume diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..dc10c16 --- /dev/null +++ b/TODO.md @@ -0,0 +1,100 @@ +I need to implement these. + +I'm adding them here for quick reference. + +```bash +pigeon status +# => BLOBS: 10,234 +# => PEERS: 26 +# => VERSION: 0.0.1 +# => FOO: BAR + +pigeon identity new +# => @ajgdylxeifojlxpbmen3exlnsbx8buspsjh37b/ipvi=.ed25519 + +pigeon identity show +# => @ajgdylxeifojlxpbmen3exlnsbx8buspsjh37b/ipvi=.ed25519 + +pigeon blob set '"Lol, data"' +# => &2e7a0bc31f3c4fe6114051c3a56c8ed8a030b3b394df7d29d37648e9b8cbf54b.sha256 + +# Or use echo for big files: +echo "Lol, data"' > pigeon blob set +# => &2e7a0bc31f3c4fe6114051c3a56c8ed8a030b3b394df7d29d37648e9b8cbf54b.sha256 + + +pigeon blob get "&2e7a0bc31f3c4fe6114051c3a56c8ed8a030b3b394df7d29d37648e9b8cbf54b.sha256" +# => "Lol, data" + +pigeon message new my_message +# => "Switched to message `my_message` + +pigeon message current # Show active log entry. +# => author: @ajgdylxeifojlxpbmen3exlnsbx8buspsjh37b/ipvi=.ed25519 +# => depth: 1 +# => kind: &82244417f956ac7c599f191593f7e441a4fafa20a4158fd52e154f1dc4c8ed92.sha256 +# => prev: %jvKh9yoiEJaePzoWCF1nnqpIlPgTk9FHEtqczQbvzGM=.sha256 +# => +# => + +pigeon message append --name=2e7a0bc3 --value=2e7a0bc3 +# => \n +# => This needs to be cleaner. +# => No one likes the way it is right now. +# => We will come back to this monstrosity later. + +pigeon message save +# => author: @ajgdylxeifojlxpbmen3exlnsbx8buspsjh37b/ipvi=.ed25519 +# => depth: 1 +# => kind: &82244417f956ac7c599f191593f7e441a4fafa20a4158fd52e154f1dc4c8ed92.sha256 +# => prev: %jvKh9yoiEJaePzoWCF1nnqpIlPgTk9FHEtqczQbvzGM=.sha256 +# => +# => &ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb.sha256:&2e7a0bc31f3c4fe6114051c3a56c8ed8a030b3b394df7d29d37648e9b8cbf54b.sha256 +# => + +pigeon message find %g0Fs9yoiEJaePzoWCF1nnqpIlPgTk9FHEtqczQbvzGM=.sha256 +# => author: @ajgdylxeifojlxpbmen3exlnsbx8buspsjh37b/ipvi=.ed25519 +# => depth: 1 +# => kind: &82244417f956ac7c599f191593f7e441a4fafa20a4158fd52e154f1dc4c8ed92.sha256 +# => prev: %jvKh9yoiEJaePzoWCF1nnqpIlPgTk9FHEtqczQbvzGM=.sha256 +# => +# => &ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb.sha256:&2e7a0bc31f3c4fe6114051c3a56c8ed8a030b3b394df7d29d37648e9b8cbf54b.sha256 +# => + +pigeon message find-all --author=@ajgdylxeifojlxpbmen3exlnsbx8buspsjh37b/ipvi=.ed25519 --since=1 +# => author: @ajgdylxeifojlxpbmen3exlnsbx8buspsjh37b/ipvi=.ed25519 +# => depth: 1 +# => kind: &82244417f956ac7c599f191593f7e441a4fafa20a4158fd52e154f1dc4c8ed92.sha256 +# => prev: %jvKh9yoiEJaePzoWCF1nnqpIlPgTk9FHEtqczQbvzGM=.sha256 +# => +# => &ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb.sha256:&2e7a0bc31f3c4fe6114051c3a56c8ed8a030b3b394df7d29d37648e9b8cbf54b.sha256 +# => +# => author: @ajgdylxeifojlxpbmen3exlnsbx8buspsjh37b/ipvi=.ed25519 +# => depth: 2 +# => kind: &82244417f956ac7c599f191593f7e441a4fafa20a4158fd52e154f1dc4c8ed92.sha256 +# => prev: %jvKh9yoiEJaePzoWCF1nnqpIlPgTk9FHEtqczQbvzGM=.sha256 +# => +# => &ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb.sha256:&2e7a0bc31f3c4fe6114051c3a56c8ed8a030b3b394df7d29d37648e9b8cbf54b.sha256 +# => + +pigeon peer add @m0LEP+0NrGqu1wT8/4a3nOPuRBM+DrMpUahDZ3/cDi8=.ed25519 +# => + +pigeon peer remove @78daXMc/BOq5F1RWLMN4zgPVBVLqA4ShkLgE6z9OUGQ=.ed25519 +# => + +pigeon peer block @GOl+398b2kWeLi6+DCcU0i3AWD6vWmUtocBVYbpkpNk=.ed25519 +# => + +pigeon peer all +# => @c8hovH5OOzNJ1SXUsIN+zI23xMcvGdEbs3ZJgzpthrw=.ed25519 +# => @GOl+398b2kWeLi6+DCcU0i3AWD6vWmUtocBVYbpkpNk=.ed25519 +# => @m0LEP+0NrGqu1wT8/4a3nOPuRBM+DrMpUahDZ3/cDi8=.ed25519 + +pigeon bundle create +# => (creates @GOl+398b2kWeLi6+DCcU0i3AWD6vWmUtocBVYbpkpNk=.ed25519.pigeon) + +pigeon bundle consume @GOl+398b2kWeLi6+DCcU0i3AWD6vWmUtocBVYbpkpNk=.ed25519.pigeon +# => + +``` diff --git a/dist/pigeon.rb b/dist/pigeon.rb new file mode 100644 index 0000000..f1936ea --- /dev/null +++ b/dist/pigeon.rb @@ -0,0 +1,6 @@ +require_relative File.join("pigeon", "config.rb") +require_relative File.join("pigeon", "storage.rb") +require_relative File.join("pigeon", "key_pair.rb") + +module Pigeon +end diff --git a/dist/pigeon/config.rb b/dist/pigeon/config.rb new file mode 100644 index 0000000..43f9ff3 --- /dev/null +++ b/dist/pigeon/config.rb @@ -0,0 +1,6 @@ + +module Pigeon + module Config + VERSION = "0.0.1" + end +end diff --git a/dist/pigeon/key_pair.rb b/dist/pigeon/key_pair.rb new file mode 100644 index 0000000..d5ddee3 --- /dev/null +++ b/dist/pigeon/key_pair.rb @@ -0,0 +1,43 @@ +require "ed25519" +require "securerandom" +require "base64" + +module Pigeon + # This is a wrapper around the `ed25519` gem to + # help us maintain our sanity when the Gem's API + # changes. + class KeyPair + HEADER, FOOTER = ["@", ".ed25519"] + + def self.current + raise "TODO" + end + + # `seed` is a 32-byte seed value from which + # the key should be derived + def initialize(seed = SecureRandom.random_bytes(Ed25519::KEY_SIZE)) + @seed = seed + @raw_key = Ed25519::SigningKey.new(seed) + end + + def private_key + @private_key ||= Base64.strict_encode64(@seed) + end + + def public_key + bytes = @raw_key.verify_key.to_bytes + b64 = Base64.strict_encode64(bytes) + + @public_key ||= [HEADER, b64, FOOTER].join("") + end + + def save! + { + public_key: public_key, + private_key: private_key, + }.map do |k, v| + Pigeon::Storage.current.save_conf(k, v) + end + end + end +end diff --git a/dist/pigeon/storage.rb b/dist/pigeon/storage.rb new file mode 100644 index 0000000..a63d363 --- /dev/null +++ b/dist/pigeon/storage.rb @@ -0,0 +1,75 @@ +require "pry" +require "digest" + +module Pigeon + class Storage + ROOT_DIR = ".pigeon" + CONF_DIR = "conf" + BLOB_DIR = "blobs" + + def self.current + @current ||= self.new + end + + def initialize + unless initialized? + create_root_dir + create_conf_dir + create_blob_dir + end + end + + def save_conf(key, value) + path = conf_path_for(key) + File.write(path, value.to_s) + end + + def get_conf(key) + File.read(conf_path_for(key)) + end + + def set_blob(data) + hash = Digest::SHA256.hexdigest(data) + path = blob_path_for(hash) + + File.write(path, data) + end + + def initialized? + File.directory?(root_dir) + end + + private + + def blob_dir + @blob_dir ||= File.join(ROOT_DIR, BLOB_DIR, "sha256") + end + + def root_dir + @root_dir ||= File.join(ROOT_DIR) + end + + # WARNING: Side effects. Im in a hurry. -RC + def blob_path_for(hex_hash_string) + first_part = File.join(blob_dir, hex_hash_string[0, 2]) + FileUtils.mkdir_p(first_part) + File.join(first_part, hex_hash_string[2..-1]) + end + + def conf_path_for(key) + File.join(conf_dir, key.to_s) + end + + def create_conf_dir + FileUtils.mkdir_p(File.join(ROOT_DIR, CONF_DIR)) + end + + def create_blob_dir + FileUtils.mkdir_p(blob_dir) + end + + def create_root_dir + FileUtils.mkdir_p(root_dir) + end + end +end diff --git a/pigeon-cli b/pigeon-cli new file mode 100755 index 0000000..f4d0994 --- /dev/null +++ b/pigeon-cli @@ -0,0 +1,63 @@ +#!/usr/bin/env ruby + +require_relative File.join("dist", "pigeon") + +require "thor" + +module Pigeon + class Identity < Thor + class RoostAlreadyExists < StandardError; end + + desc "new", "Creates a new identiy in `.pigeon` directory if none exists" + + def new + # TODO: --force flag + # TODO: --seed flag + if Dir.exist?(Pigeon::Storage::ROOT_DIR) + puts "Pigeon has detected a `.pigeon` directory. + Refusing to overwrite existing Pigeon config. + Remove roost or switch to a different directory." + raise RoostAlreadyExists + end + kp = Pigeon::KeyPair.new() + kp.save! + puts kp.public_key + end + + desc "show", "Prints a base64 identiy string to STDOUT" + + def show + puts Pigeon::Storage.current.get_conf("public_key") + end + end + + class Blob < Thor + desc "set", "Copy arbitrary binary data into the roost" + + def set(data) + Pigeon::Storage.current.set_blob(data) + end + + desc "get", "Read arbitrary data from the roost" + + def get + raise "WIP" + end + end + + class CLI < Thor + desc "status", "Show various information about the `.pigeon` directory" + + def status + puts "Version: #{Config::VERSION}" + end + + desc "identity SUBCOMMAND ...ARGS", "Manage `.pigeon` identity" + subcommand "identity", Identity + + desc "blob SUBCOMMAND ...ARGS", "Manage blob storage" + subcommand "blob", Blob + end +end + +Pigeon::CLI.start(ARGV) diff --git a/spec/pigeon/config_spec.rb b/spec/pigeon/config_spec.rb new file mode 100644 index 0000000..56c951e --- /dev/null +++ b/spec/pigeon/config_spec.rb @@ -0,0 +1,7 @@ +require "spec_helper" + +RSpec.describe Pigeon::Config do + it "has a `foo`" do + expect(Pigeon::Config::VERSION).to eq("0.0.1") + end +end diff --git a/spec/pigeon/key_pair_spec.rb b/spec/pigeon/key_pair_spec.rb new file mode 100644 index 0000000..2f62ad8 --- /dev/null +++ b/spec/pigeon/key_pair_spec.rb @@ -0,0 +1,19 @@ +require "spec_helper" + +RSpec.describe Pigeon::KeyPair do + FAKE_SEED = "\x15\xB1\xA8\x1D\xE1\x1Cx\xF0" \ + "\xC6\xDCK\xDE\x9A\xB7>\x86o\x92\xEF\xB7\x17" \ + ")\xFF\x01E\b$b)\xC9\x82\b" + + let(:kp) { Pigeon::KeyPair.new(FAKE_SEED) } + + it "generates a pair from a seed" do + x = "@7n/g0ca9FFWvMkXy2TMwM7bdMn6tNiEHKzrFX+CzAmQ=.ed25519" + expect(kp.public_key).to eq(x) + y = "FbGoHeEcePDG3Evemrc+hm+S77cXKf8BRQgkYinJggg=" + expect(kp.private_key).to eq(y) + end + + # TODO Add fakefs https://github.com/fakefs/fakefss + it "saves keypairs to disk" +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..80611b4 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,9 @@ +require "pry" +require "simplecov" +SimpleCov.start +require_relative File.join("..", "dist", "pigeon") +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + config.disable_monkey_patching! + config.order = :random +end