Skip to content

Commit

Permalink
feat: transaction signature
Browse files Browse the repository at this point in the history
  • Loading branch information
BastienFaivre committed Jan 8, 2025
1 parent f0d71d2 commit 92e9aa0
Show file tree
Hide file tree
Showing 11 changed files with 228 additions and 38 deletions.
84 changes: 74 additions & 10 deletions dog/src/behaviour.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,24 @@ use std::{
use futures::FutureExt;
use futures_timer::Delay;
use libp2p::{
identity::Keypair,
swarm::{
behaviour::ConnectionEstablished, ConnectionClosed, FromSwarm, NetworkBehaviour, ToSwarm,
},
PeerId,
};
use lru::LruCache;
use quick_protobuf::{MessageWrite, Writer};
use rand::seq::IteratorRandom;

use crate::{
config::Config,
dog::{Controller, Route, Router},
error::PublishError,
handler::{Handler, HandlerEvent, HandlerIn},
protocol::SIGNING_PREFIX,
rpc::Sender,
rpc_proto::proto,
transform::{DataTransform, IdentityTransform},
types::{
ControlAction, HaveTx, PeerConnections, RawTransaction, ResetRoute, RpcOut, Transaction,
Expand All @@ -32,11 +36,11 @@ use crate::{
/// Determines if published transaction should be signed or not.
#[derive(Debug)]
pub enum TransactionAuthenticity {
// /// Transaction signing is enabled. The author will be the owner of the key and
// /// the sequence number will be linearly increasing.
// Signed(Keypair),
/// Transaction signing is enabled. The author will be the owner of the key and
/// the sequence number will be linearly increasing.
Signed(Keypair),
/// Transaction signing is disabled. The specified [`PeerId`] will be used as the author
/// of all published transactions. The sequence number will be randomized.
/// of all published transactions. The sequence number will be linearly increasing.
Author(PeerId),
}

Expand All @@ -61,12 +65,12 @@ pub enum Event {

// A data structure for storing configuration for publishing transactions.
enum PublishConfig {
// Signing {
// keypair: Keypair,
// author: PeerId,
// inline_key: Option<Vec<u8>>,
// last_seqno: SequenceNumber,
// },
Signing {
keypair: Keypair,
author: PeerId,
inline_key: Option<Vec<u8>>,
last_seqno: SequenceNumber,
},
Author {
author: PeerId,
last_seqno: SequenceNumber,
Expand Down Expand Up @@ -102,6 +106,7 @@ impl SequenceNumber {
impl PublishConfig {
pub(crate) fn get_own_id(&self) -> PeerId {
match self {
Self::Signing { author, .. } => *author,
Self::Author { author, .. } => *author,
}
}
Expand All @@ -110,6 +115,26 @@ impl PublishConfig {
impl From<TransactionAuthenticity> for PublishConfig {
fn from(authenticity: TransactionAuthenticity) -> Self {
match authenticity {
TransactionAuthenticity::Signed(keypair) => {
let public_key = keypair.public();
let key_enc = public_key.encode_protobuf();
let key = if key_enc.len() <= 42 {
// The public key can be inlined in [`rpc_proto::proto::Transaction::from`], so we
// don't include it specifically in the
// [`rpc_proto::proto::Transaction::key`] field.
None
} else {
// Include the protobuf encoding of the public key in the message.
Some(key_enc)
};

PublishConfig::Signing {
keypair,
author: public_key.to_peer_id(),
inline_key: key,
last_seqno: SequenceNumber::new(),
}
}
TransactionAuthenticity::Author(author) => PublishConfig::Author {
author,
last_seqno: SequenceNumber::new(),
Expand Down Expand Up @@ -248,13 +273,52 @@ where

fn build_raw_transaction(&mut self, data: Vec<u8>) -> Result<RawTransaction, PublishError> {
match &mut self.publish_config {
PublishConfig::Signing {
ref keypair,
author,
inline_key,
last_seqno,
} => {
let seqno = last_seqno.next();

let signature = {
let transaction = proto::Transaction {
from: author.to_bytes(),
seqno,
data: data.clone(),
signature: vec![],
key: vec![],
};

let mut buf = Vec::with_capacity(transaction.get_size());
let mut writer = Writer::new(&mut buf);

transaction
.write_message(&mut writer)
.expect("Encoding to succeed");

let mut signature_bytes = SIGNING_PREFIX.to_vec();
signature_bytes.extend_from_slice(&buf);
keypair.sign(&signature_bytes)?
};

Ok(RawTransaction {
from: *author,
seqno,
data,
signature: Some(signature.to_vec()),
key: inline_key.clone(),
})
}
PublishConfig::Author { author, last_seqno } => {
let seqno = last_seqno.next();

Ok(RawTransaction {
from: *author,
seqno,
data,
signature: None,
key: None,
})
}
}
Expand Down
9 changes: 8 additions & 1 deletion dog/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::{
pub enum ValidationMode {
/// This is the default setting. This settings validates all fields of the transaction.
Strict,
/// This setting does not check any fields of the transaction.
/// This setting only checks that the author field is valid
None,
}

Expand Down Expand Up @@ -222,6 +222,13 @@ impl ConfigBuilder {
self
}

/// Determines the level of validation used when receiving transactions. See [`ValidationMode`]
/// for the available types. the default is `ValidationMode::Strict`.
pub fn validation_mode(&mut self, validation_mode: ValidationMode) -> &mut Self {
self.config.protocol.validation_mode = validation_mode;
self
}

/// Constructs a `Config` from the parameters set in the builder.
pub fn build(&self) -> Result<Config, &'static str> {
// TODO: validate config
Expand Down
1 change: 1 addition & 0 deletions dog/src/dog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ impl Display for Route {
}

pub(crate) struct Router {
// TODO: is it better to use a HashMap<PeerId, HashSet<PeerId>>?
disabled_routes: Vec<Route>,
}

Expand Down
13 changes: 12 additions & 1 deletion dog/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
use libp2p::identity::SigningError;

#[derive(Debug)]
pub enum PublishError {
/// This transaction has already been published.
Duplicate,
/// An error occurred while signing the transaction.
SigningError(SigningError),
/// There were no peers to send this transaction to.
InsufficientPeers,
/// The overal transaction was too large.
Expand Down Expand Up @@ -34,11 +38,18 @@ impl From<std::io::Error> for PublishError {
}
}

impl From<SigningError> for PublishError {
fn from(error: SigningError) -> Self {
PublishError::SigningError(error)
}
}

#[derive(Debug)]
pub enum ValidationError {
/// The PeerId was invalid.
InvalidPeerId,
// TODO: complete with more error types as the development progresses.
/// The signature was invalid.
InvalidSignature,
}

impl std::fmt::Display for ValidationError {
Expand Down
8 changes: 8 additions & 0 deletions dog/src/generated/dog/pb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ pub struct Transaction {
pub from: Vec<u8>,
pub seqno: u64,
pub data: Vec<u8>,
pub signature: Vec<u8>,
pub key: Vec<u8>,
}

impl<'a> MessageRead<'a> for Transaction {
Expand All @@ -65,6 +67,8 @@ impl<'a> MessageRead<'a> for Transaction {
Ok(10) => msg.from = r.read_bytes(bytes)?.to_owned(),
Ok(16) => msg.seqno = r.read_uint64(bytes)?,
Ok(26) => msg.data = r.read_bytes(bytes)?.to_owned(),
Ok(34) => msg.signature = r.read_bytes(bytes)?.to_owned(),
Ok(42) => msg.key = r.read_bytes(bytes)?.to_owned(),
Ok(t) => { r.read_unknown(bytes, t)?; }
Err(e) => return Err(e),
}
Expand All @@ -79,12 +83,16 @@ impl MessageWrite for Transaction {
+ if self.from.is_empty() { 0 } else { 1 + sizeof_len((&self.from).len()) }
+ if self.seqno == 0u64 { 0 } else { 1 + sizeof_varint(*(&self.seqno) as u64) }
+ if self.data.is_empty() { 0 } else { 1 + sizeof_len((&self.data).len()) }
+ if self.signature.is_empty() { 0 } else { 1 + sizeof_len((&self.signature).len()) }
+ if self.key.is_empty() { 0 } else { 1 + sizeof_len((&self.key).len()) }
}

fn write_message<W: WriterBackend>(&self, w: &mut Writer<W>) -> Result<()> {
if !self.from.is_empty() { w.write_with_tag(10, |w| w.write_bytes(&**&self.from))?; }
if self.seqno != 0u64 { w.write_with_tag(16, |w| w.write_uint64(*&self.seqno))?; }
if !self.data.is_empty() { w.write_with_tag(26, |w| w.write_bytes(&**&self.data))?; }
if !self.signature.is_empty() { w.write_with_tag(34, |w| w.write_bytes(&**&self.signature))?; }
if !self.key.is_empty() { w.write_with_tag(42, |w| w.write_bytes(&**&self.key))?; }
Ok(())
}
}
Expand Down
2 changes: 2 additions & 0 deletions dog/src/generated/rpc.proto
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ message Transaction {
bytes from = 1;
uint64 seqno = 2;
bytes data = 3;
bytes signature = 4;
bytes key = 5;
}

message ControlMessage {
Expand Down
Loading

0 comments on commit 92e9aa0

Please sign in to comment.