diff --git a/dist/pigeon.rb b/dist/pigeon.rb index add4602..9d83575 100644 --- a/dist/pigeon.rb +++ b/dist/pigeon.rb @@ -60,6 +60,12 @@ module Pigeon # /Constants for internal use only class Helpers + VERFIY_ERROR = "Expected field `%s` to equal %s, got: %s" + MSG_SIZE_ERROR = "Messages cannot have more than 64 keys. Got %s." + + class VerificationError < StandardError; end + class MessageSizeError < StandardError; end + B32_ENC = { "00000" => "0", "00001" => "1", "00010" => "2", "00011" => "3", "00100" => "4", "00101" => "5", "00110" => "6", "00111" => "7", @@ -145,7 +151,7 @@ module Pigeon verify_key.verify(binary_signature, string) end - def assert(field, actual, expected) + def self.assert(field, actual, expected) unless actual == expected message = VERFIY_ERROR % [field, actual || "nil", expected || "nil"] raise VerificationError, message @@ -171,24 +177,30 @@ module Pigeon message end - def publish_message(db, msg) - return db.read_message(multihash) if db.message?(multihash) + def self.verify_message(db, msg) + msg_hash = msg.multihash + body = msg.body key_count = body.count + author = msg.author + signature = msg.signature + + return db.read_message(msg_hash) if db.message?(msg_hash) + if key_count > 64 msg = MSG_SIZE_ERROR % key_count raise MessageSizeError, msg end count = db.get_message_count_for(author.multihash) expected_prev = db.get_message_by_depth(author.multihash, count - 1) || Pigeon::NOTHING - assert("depth", count, depth) + assert("depth", count, msg.depth) # TODO: Re-visit this. Our current verification method # is probably too strict and won't allow for partial # verification of feeds. - assert("lipmaa", Helpers.lipmaa(depth), lipmaa) - assert("prev", prev, expected_prev) - verify_signature + assert("lipmaa", Helpers.lipmaa(msg.depth), msg.lipmaa) + assert("prev", msg.prev, expected_prev) + tpl = msg.template.render_without_signature + Helpers.verify_string(author, signature, tpl) msg.freeze - db.save_message(msg) msg end diff --git a/dist/pigeon/database.rb b/dist/pigeon/database.rb index 2d2ec1f..728a8ca 100644 --- a/dist/pigeon/database.rb +++ b/dist/pigeon/database.rb @@ -21,7 +21,11 @@ module Pigeon def all_peers(); store.all_peers(); end def all_blocks(); store.all_blocks(); end def message?(multihash); store.message?(multihash); end - def save_message(msg_obj); store.save_message(msg_obj); end + + def save_message(msg_obj) + store.insert_message(Helpers.verify_message(self, msg_obj)) + end + def read_message(multihash); store.read_message(multihash); end def get_message_count_for(multihash) @@ -84,13 +88,14 @@ module Pigeon # Store a message that someone (not the LocalIdentity) # has authored. def ingest(author:, body:, depth:, kind:, lipmaa:, prev:, signature:) - self.save_message(Message.new(author: RemoteIdentity.new(author), - kind: kind, - body: body, - prev: prev, - lipmaa: lipmaa, - signature: signature, - depth: depth)) + msg = Message.new(author: RemoteIdentity.new(author), + kind: kind, + body: body, + prev: prev, + lipmaa: lipmaa, + signature: signature, + depth: depth) + save_message(msg) end private @@ -98,7 +103,7 @@ module Pigeon attr_reader :store def init_local_identity(new_seed) - key = store.get_config(SEED_CONFIG_KEY) + key = get_config(SEED_CONFIG_KEY) if key @local_identity = LocalIdentity.new(key) else diff --git a/dist/pigeon/message.rb b/dist/pigeon/message.rb index d21b0b1..313907e 100644 --- a/dist/pigeon/message.rb +++ b/dist/pigeon/message.rb @@ -4,12 +4,6 @@ module Pigeon class Message attr_reader :author, :kind, :body, :signature, :depth, :lipmaa, :prev - class VerificationError < StandardError; end - class MessageSizeError < StandardError; end - - VERFIY_ERROR = "Expected field `%s` to equal %s, got: %s" - MSG_SIZE_ERROR = "Messages cannot have more than 64 keys. Got %s." - def render template.render.chomp end @@ -21,13 +15,6 @@ module Pigeon "#{MESSAGE_SIGIL}#{sha256}#{BLOB_FOOTER}" end - private - - def verify_signature - tpl = template.render_without_signature - Helpers.verify_string(author, signature, tpl) - end - def initialize(author:, kind:, body:, diff --git a/dist/pigeon/storage.rb b/dist/pigeon/storage.rb index 0d95ec6..64a4c16 100644 --- a/dist/pigeon/storage.rb +++ b/dist/pigeon/storage.rb @@ -100,7 +100,7 @@ module Pigeon read { store[MESG_NS].fetch(multihash) } end - def save_message(msg) + def insert_message(msg) write do return msg if store[MESG_NS][msg.multihash] insert_and_update_index(msg) diff --git a/spec/pigeon/message_spec.rb b/spec/pigeon/message_spec.rb index 0eb0eec..46a1523 100644 --- a/spec/pigeon/message_spec.rb +++ b/spec/pigeon/message_spec.rb @@ -31,12 +31,12 @@ RSpec.describe Pigeon::Message do it "discards a draft after signing" do db.publish_draft(draft) - expect { db.current_draft }.to raise_error("NO DRAFT FOUND") + expect(db.current_draft).to be(nil) end it "creates a single message" do message = db.publish_draft(draft) - expect(message.author.multihash).to eq(Pigeon::LocalIdentity.current.multihash) + expect(message.author.multihash).to eq(db.local_identity.multihash) expect(message.body).to eq(draft.body) expect(message.depth).to eq(0) expect(message.kind).to eq("unit_test") @@ -100,7 +100,7 @@ RSpec.describe Pigeon::Message do end expect do create_message(body) - end.to raise_error(Pigeon::Message::MessageSizeError, error) + end.to raise_error(Pigeon::Helpers::MessageSizeError, error) end it "verifies accuracy of signatures" do @@ -110,7 +110,7 @@ RSpec.describe Pigeon::Message do plaintext = template.render_without_signature # Make fake pairs of data for cross-checking - key1 = Pigeon::LocalIdentity.current.instance_variable_get(:@signing_key) + key1 = db.local_identity.instance_variable_get(:@signing_key) key2 = Ed25519::SigningKey.new(secret) sig1 = key1.sign(plaintext) @@ -128,20 +128,22 @@ RSpec.describe Pigeon::Message do end it "crashes on forged fields" do + tokens = [ + [:AUTHOR, "@DYdgK1KUInVtG3lS45hA1HZ-jTuvfLKsxDpXPFCve04=.ed25519"], + [:KIND, "invalid"], + [:PREV, "NONE"], + [:DEPTH, 10], + [:LIPMAA, Pigeon::Helpers.lipmaa(10)], + [:HEADER_END], + [:BODY_ENTRY, "duplicate", "This key is a duplicate."], + [:SIGNATURE, "DN7yPTE-m433ND3jBL4oM23XGxBKafjq0Dp9ArBQa_TIGU7DmCxTumieuPBN-NKxlx_0N7-c5zjLb5XXVHYPCQ==.sig.ed25519"], + [:MESSAGE_END], + ] + e = Pigeon::Helpers::VerificationError m = "Expected field `depth` to equal 0, got: 10" expect do - Pigeon::Parser.parse(db, [ - [:AUTHOR, "@DYdgK1KUInVtG3lS45hA1HZ-jTuvfLKsxDpXPFCve04=.ed25519"], - [:KIND, "invalid"], - [:PREV, "NONE"], - [:DEPTH, 10], - [:LIPMAA, Pigeon::Helpers.lipmaa(10)], - [:HEADER_END], - [:BODY_ENTRY, "duplicate", "This key is a duplicate."], - [:SIGNATURE, "DN7yPTE-m433ND3jBL4oM23XGxBKafjq0Dp9ArBQa_TIGU7DmCxTumieuPBN-NKxlx_0N7-c5zjLb5XXVHYPCQ==.sig.ed25519"], - [:MESSAGE_END], - ]).first.save! - end.to raise_error(Pigeon::Message::VerificationError, m) + msg = Pigeon::Parser.parse(db, tokens)[0] + end.to raise_error(e, m) end # Every ASCII character that is not a letter: