Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add verification that we have already received a signature for the transaction for the selected input and validator #7

Merged
merged 1 commit into from
Apr 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sandbox_tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ serde_json = "0.8.0"
byteorder = "1.0.0"
log = "0.3.0"
rand = "0.3.0"
libc = "0.2.21"

[dependencies.exonum]
git = "ssh://git@github.com/exonum/exonum-core.git"
Expand Down
3 changes: 3 additions & 0 deletions sandbox_tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ extern crate serde;
extern crate serde_json;
extern crate bitcoin;
extern crate bitcoinrpc;
extern crate byteorder;
extern crate secp256k1;
extern crate blockchain_explorer;
#[macro_use]
extern crate log;
extern crate rand;
extern crate libc;

use std::ops::Deref;
use std::sync::{Arc, Mutex, MutexGuard};
Expand Down Expand Up @@ -43,6 +45,7 @@ mod macros;
#[cfg(test)]
mod tests;
pub mod helpers;
pub mod secp256k1_hack;

pub const ANCHORING_VALIDATOR: u32 = VALIDATOR_0;
pub const ANCHORING_FREQUENCY: u64 = 10;
Expand Down
85 changes: 85 additions & 0 deletions sandbox_tests/src/secp256k1_hack.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use std::mem;

use libc::c_void;
use byteorder::{LittleEndian, ByteOrder};
use bitcoin::blockdata::script::Script;
use bitcoin::blockdata::transaction::SigHashType;
use secp256k1::ffi;
use secp256k1::{ContextFlag, Secp256k1};
use secp256k1::{Message, Signature};
use secp256k1::key::SecretKey;
use secp256k1::key;
use secp256k1::Error;


use anchoring_btc_service::transactions::RawBitcoinTx;


/// The structure with the same memory representation as the `secp256k1::Secp256k1`.
#[derive(Clone, Copy)]
struct Context {
pub ctx: *mut ffi::Context,
pub caps: ContextFlag,
}

impl Context {
/// Same as the 'secp256k1::Secp256k1::sign` but has a nonce argument.
pub fn sign(&self, msg: &Message, sk: &key::SecretKey, nonce: u64) -> Result<Signature, Error> {
if self.caps == ContextFlag::VerifyOnly || self.caps == ContextFlag::None {
return Err(Error::IncapableContext);
}

let nonce_array = {
let mut data = [0; 32];
LittleEndian::write_u64(&mut data, nonce);
data
};

let mut ret = unsafe { ffi::Signature::blank() };
unsafe {
// We can assume the return value because it's not possible to construct
// an invalid signature from a valid `Message` and `SecretKey`
assert_eq!(ffi::secp256k1_ecdsa_sign(self.ctx,
&mut ret,
msg.as_ptr(),
sk.as_ptr(),
ffi::secp256k1_nonce_function_rfc6979,
nonce_array.as_ptr() as *const c_void),
1);
}
Ok(Signature::from(ret))
}
}

fn get_ffi_context(ctx: &mut Secp256k1) -> Context {
unsafe {
let ctx_ptr: *mut Context = mem::transmute(ctx as *mut Secp256k1);
*ctx_ptr
}
}

fn sign_with_nonce(ctx: &mut Secp256k1,
msg: &Message,
sk: &key::SecretKey,
nonce: u64)
-> Result<Signature, Error> {
let ctx = get_ffi_context(ctx);
ctx.sign(msg, sk, nonce)
}

pub fn sign_tx_input_with_nonce(tx: &RawBitcoinTx,
input: usize,
subscript: &Script,
sec_key: &SecretKey,
nonce: u64)
-> Vec<u8> {
let sighash = tx.signature_hash(input, subscript, SigHashType::All.as_u32());
// Make signature
let mut context = Secp256k1::new();
let msg = Message::from_slice(&sighash[..]).unwrap();
let sign = sign_with_nonce(&mut context, &msg, sec_key, nonce).unwrap();
// Serialize signature
let mut sign_data = sign.serialize_der(&context);
sign_data.push(SigHashType::All.as_u32() as u8);
sign_data
}
56 changes: 55 additions & 1 deletion sandbox_tests/tests/anchoring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ use exonum::messages::Message;
use sandbox::sandbox_tests_helper::{SandboxState, add_one_height_with_transactions};

use anchoring_btc_service::sandbox::Request;
use anchoring_btc_service::transactions::{TransactionBuilder, AnchoringTx};
use anchoring_btc_service::transactions::{TransactionBuilder, AnchoringTx, verify_input};
use anchoring_btc_service::MsgAnchoringSignature;
use anchoring_btc_sandbox::{RpcError, anchoring_sandbox};
use anchoring_btc_sandbox::helpers::*;
use anchoring_btc_sandbox::secp256k1_hack::sign_tx_input_with_nonce;

// We anchor first block
// problems: None
Expand Down Expand Up @@ -583,3 +584,56 @@ fn test_anchoring_signature_nonexistent_input() {
let signs_after = dump_signatures(&sandbox, &tx.id());
assert_eq!(signs_before, signs_after);
}

// We received signature message with correct input but different signature
// problems: None
// result: we ignore it
#[test]
fn test_anchoring_signature_input_with_different_correct_signature() {
let _ = ::blockchain_explorer::helpers::init_logger();

let (sandbox, client, mut anchoring_state) = anchoring_sandbox(&[]);
let sandbox_state = SandboxState::new();

anchor_first_block(&sandbox, &client, &sandbox_state, &mut anchoring_state);
anchor_first_block_lect_normal(&sandbox, &client, &sandbox_state, &mut anchoring_state);

let signature_msgs = anchoring_state.latest_anchored_tx_signatures();
let tx = {
let mut tx = anchoring_state.latest_anchored_tx().clone();
tx.0.input[0].script_sig = Script::new();
tx
};

let different_sign_msg = {
let (redeem_script, addr) = anchoring_state.common.redeem_script();
let pub_key = &anchoring_state.common.validators[1];
let priv_key = &anchoring_state.priv_keys(&addr)[1];

let different_signature =
sign_tx_input_with_nonce(&tx, 0, &redeem_script, priv_key.secret_key(), 2);
assert!(verify_input(&tx,
0,
&redeem_script,
pub_key,
different_signature.as_ref()));
assert!(different_signature != signature_msgs[1].signature());

MsgAnchoringSignature::new(&sandbox.p(1),
1,
tx.clone(),
0,
different_signature.as_ref(),
sandbox.s(1))
};
assert!(signature_msgs[1] != different_sign_msg);

let signs_before = dump_signatures(&sandbox, &tx.id());
// Try to commit tx
add_one_height_with_transactions(&sandbox,
&sandbox_state,
&[different_sign_msg.raw().clone()]);
// Ensure that service ignore tx
let signs_after = dump_signatures(&sandbox, &tx.id());
assert_eq!(signs_before, signs_after);
}
29 changes: 28 additions & 1 deletion src/service/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,12 @@ impl<'a> AnchoringSchema<'a> {
MapTable::new(prefix, self.view)
}

// Key is tuple (txid, validator_id, input), see `known_signature_id`.
pub fn known_signatures(&self) -> MapTable<View, [u8], MsgAnchoringSignature> {
let prefix = vec![ANCHORING_SERVICE as u8, 6];
MapTable::new(prefix, self.view)
}

pub fn current_anchoring_config(&self) -> Result<AnchoringConfig, StorageError> {
let actual = Schema::new(self.view).get_actual_configuration()?;
Ok(self.parse_config(&actual))
Expand Down Expand Up @@ -245,6 +251,18 @@ impl<'a> AnchoringSchema<'a> {
.map(|x| x.is_some())
}

pub fn add_known_signature(&self, msg: MsgAnchoringSignature) -> Result<(), StorageError> {
let txid = msg.tx().id();
let signature_id = Self::known_signature_id(&msg);
if let Some(sign_msg) = self.known_signatures().get(&signature_id)? {
warn!("Received another signature for given tx propose msg={:#?}", sign_msg);
} else {
self.signatures(&txid).append(msg.clone())?;
self.known_signatures().put(&signature_id, msg)?;
}
Ok(())
}

pub fn state_hash(&self) -> Result<Vec<Hash>, StorageError> {
let cfg = Schema::new(self.view).get_actual_configuration()?;
let service_id = ANCHORING_SERVICE.to_string();
Expand All @@ -262,6 +280,15 @@ impl<'a> AnchoringSchema<'a> {
}
}

fn known_signature_id(msg: &MsgAnchoringSignature) -> Vec<u8> {
Copy link
Contributor

@slowli slowli Mar 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe create a new Exonum datatype encompassing a (txid, validator, input_index) triple, and use it here? The current approach may be not sustainable; e.g., we may want to provide proofs to light clients and/or change general serialization logic in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can not represent the custom type as &[u8].

let txid = msg.tx().id();

let mut id = vec![txid.as_ref(), [0; 8].as_ref()].concat();
BigEndian::write_u32(&mut id[32..36], msg.validator());
BigEndian::write_u32(&mut id[36..40], msg.input());
id
}

fn parse_config(&self, cfg: &StoredConfiguration) -> AnchoringConfig {
let service_id = ANCHORING_SERVICE.to_string();
from_value(cfg.services[&service_id].clone()).unwrap()
Expand Down Expand Up @@ -293,7 +320,7 @@ impl MsgAnchoringSignature {
error!("Received msg with incorrect signature content={:#?}", self);
return Ok(());
}
schema.signatures(&tx.id()).append(self.clone())
schema.add_known_signature(self.clone())
} else {
Ok(())
}
Expand Down