Test bitcoin-serai

Also resolves a few rough edges.
This commit is contained in:
Luke Parker
2023-03-20 04:46:27 -04:00
parent 6a2a353b91
commit 7fc8630d39
10 changed files with 372 additions and 35 deletions

View File

@@ -2,11 +2,12 @@
pub use bitcoin;
/// Cryptographic helpers.
#[cfg(feature = "hazmat")]
pub mod crypto;
#[cfg(not(feature = "hazmat"))]
pub(crate) mod crypto;
/// Wallet functionality to create transactions.
pub mod wallet;
/// A minimal asynchronous Bitcoin RPC client.
pub mod rpc;
#[cfg(test)]
mod tests;

View File

@@ -14,11 +14,17 @@ use bitcoin::{
Txid, Transaction, BlockHash, Block,
};
#[derive(Clone, PartialEq, Eq, Debug, Deserialize)]
pub struct Error {
code: isize,
message: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
enum RpcResponse<T> {
Ok { result: T },
Err { error: String },
Err { error: Error },
}
/// A minimal asynchronous Bitcoin RPC client.
@@ -29,8 +35,8 @@ pub struct Rpc(String);
pub enum RpcError {
#[error("couldn't connect to node")]
ConnectionError,
#[error("request had an error: {0}")]
RequestError(String),
#[error("request had an error: {0:?}")]
RequestError(Error),
#[error("node sent an invalid response")]
InvalidResponse,
}
@@ -69,7 +75,14 @@ impl Rpc {
}
/// Get the latest block's number.
///
/// The genesis block's 'number' is zero. They increment from there.
pub async fn get_latest_block_number(&self) -> Result<usize, RpcError> {
// getblockcount doesn't return the amount of blocks on the current chain, yet the "height"
// of the current chain. The "height" of the current chain is defined as the "height" of the
// tip block of the current chain. The "height" of a block is defined as the amount of blocks
// present when the block was created. Accordingly, the genesis block has height 0, and
// getblockcount will return 0 when it's only the only block, despite their being one block.
self.rpc_call("getblockcount", json!([])).await
}

View File

@@ -1,59 +0,0 @@
use rand_core::OsRng;
use sha2::{Digest, Sha256};
use secp256k1::{SECP256K1, Message};
use bitcoin::hashes::{Hash as HashTrait, sha256::Hash};
use k256::Scalar;
use transcript::{Transcript, RecommendedTranscript};
use frost::{
curve::Secp256k1,
Participant,
tests::{algorithm_machines, key_gen, sign},
};
use crate::{
crypto::{x_only, make_even, Schnorr},
rpc::Rpc,
};
#[test]
fn test_algorithm() {
let mut keys = key_gen::<_, Secp256k1>(&mut OsRng);
const MESSAGE: &[u8] = b"Hello, World!";
for (_, keys) in keys.iter_mut() {
let (_, offset) = make_even(keys.group_key());
*keys = keys.offset(Scalar::from(offset));
}
let algo =
Schnorr::<RecommendedTranscript>::new(RecommendedTranscript::new(b"bitcoin-serai sign test"));
let sig = sign(
&mut OsRng,
algo.clone(),
keys.clone(),
algorithm_machines(&mut OsRng, algo, &keys),
&Sha256::digest(MESSAGE),
);
SECP256K1
.verify_schnorr(
&sig,
&Message::from(Hash::hash(MESSAGE)),
&x_only(&keys[&Participant::new(1).unwrap()].group_key()),
)
.unwrap()
}
#[tokio::test]
async fn test_rpc() {
let rpc = Rpc::new("http://serai:seraidex@127.0.0.1:18443".to_string()).await.unwrap();
let latest = rpc.get_latest_block_number().await.unwrap();
assert_eq!(
rpc.get_block_number(&rpc.get_block_hash(latest).await.unwrap()).await.unwrap(),
latest
);
}

View File

@@ -42,8 +42,6 @@ pub fn address(network: Network, key: ProjectivePoint) -> Option<Address> {
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct ReceivedOutput {
// The scalar offset to obtain the key usable to spend this output.
//
// This field exists in order to support HDKD schemes.
offset: Scalar,
// The output to spend.
output: TxOut,
@@ -148,6 +146,10 @@ impl Scanner {
}
/// Scan a block.
///
/// This will also scan the coinbase transaction which is bound by maturity. If received outputs
/// must be immediately spendable, a post-processing pass is needed to remove those outputs.
/// Alternatively, scan_transaction can be called on `block.txdata[1 ..]`.
pub fn scan_block(&self, block: &Block) -> Vec<ReceivedOutput> {
let mut res = vec![];
for tx in &block.txdata {

View File

@@ -15,10 +15,13 @@ use frost::{curve::Secp256k1, Participant, ThresholdKeys, FrostError, sign::*};
use bitcoin::{
hashes::Hash,
util::sighash::{SchnorrSighashType, SighashCache, Prevouts},
OutPoint, Script, Sequence, Witness, TxIn, TxOut, PackedLockTime, Transaction, Address,
OutPoint, Script, Sequence, Witness, TxIn, TxOut, PackedLockTime, Transaction, Network, Address,
};
use crate::{crypto::Schnorr, wallet::ReceivedOutput};
use crate::{
crypto::Schnorr,
wallet::{address, ReceivedOutput},
};
#[rustfmt::skip]
// https://github.com/bitcoin/bitcoin/blob/306ccd4927a2efe325c8d84be1bdb79edeb29b04/src/policy/policy.h#L27
@@ -192,11 +195,13 @@ impl SignableTransaction {
}
/// Create a multisig machine for this transaction.
pub async fn multisig(
///
/// Returns None if the wrong keys are used.
pub fn multisig(
self,
keys: ThresholdKeys<Secp256k1>,
mut transcript: RecommendedTranscript,
) -> Result<TransactionMachine, FrostError> {
) -> Option<TransactionMachine> {
transcript.domain_separate(b"bitcoin_transaction");
transcript.append_message(b"root_key", keys.group_key().to_encoded_point(true).as_bytes());
@@ -215,13 +220,21 @@ impl SignableTransaction {
for i in 0 .. tx.input.len() {
let mut transcript = transcript.clone();
transcript.append_message(b"signing_input", u32::try_from(i).unwrap().to_le_bytes());
let offset = keys.clone().offset(self.offsets[i]);
if address(Network::Bitcoin, offset.group_key())?.script_pubkey() !=
self.prevouts[i].script_pubkey
{
None?;
}
sigs.push(AlgorithmMachine::new(
Schnorr::new(transcript),
keys.clone().offset(self.offsets[i]),
));
}
Ok(TransactionMachine { tx: self, sigs })
Some(TransactionMachine { tx: self, sigs })
}
}