Skip to content
This repository was archived by the owner on Jun 23, 2025. It is now read-only.

Commit b543d71

Browse files
authored
Merge pull request #58 from Quible-Network/tenorbiel/qui-103-ensure-coinbase-transactions-can-only-be-spent-by-block
feat(QUI-103): ensure coinbase transaction outputs are owned by the proposer
2 parents 9441d81 + 331cb5c commit b543d71

File tree

1 file changed

+125
-69
lines changed

1 file changed

+125
-69
lines changed

src/main.rs

Lines changed: 125 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use alloy_primitives::{FixedBytes, B256};
1+
use alloy_primitives::{Address, FixedBytes, B256};
22
use anyhow::anyhow;
33
use async_trait::async_trait;
44
use cert::types::{CertificateSigningRequestDetails, QuibleSignature, SignedCertificate};
@@ -11,6 +11,7 @@ use hyper::Method;
1111
use jsonrpsee::core::async_trait as jsonrpsee_async_trait;
1212
use jsonrpsee::types::error::CALL_EXECUTION_FAILED_CODE;
1313
use jsonrpsee::{server::Server, types::ErrorObjectOwned};
14+
use k256::ecdsa::SigningKey;
1415
use libp2p::{multiaddr, noise, ping, swarm::SwarmEvent, tcp, yamux};
1516
use quible_ecdsa_utils::sign_message;
1617
use serde::{Deserialize, Serialize};
@@ -215,6 +216,7 @@ async fn digest_object_output(
215216
async fn propose_block(
216217
block_number: u64,
217218
db_arc: &Arc<Surreal<AnyDb>>,
219+
node_signing_key: &SigningKey,
218220
) -> anyhow::Result<BlockRow> {
219221
println!("preparing block {}", block_number);
220222

@@ -281,8 +283,16 @@ async fn propose_block(
281283
outputs: vec![TransactionOutput::Value {
282284
value: 5,
283285

284-
// TODO: https://linear.app/quible/issue/QUI-103/ensure-coinbase-transactions-can-only-be-spent-by-block-proposer
285-
pubkey_script: vec![],
286+
pubkey_script: vec![
287+
TransactionOpCode::Dup,
288+
TransactionOpCode::Push {
289+
data: Address::from_private_key(&node_signing_key)
290+
.into_array()
291+
.to_vec(),
292+
},
293+
TransactionOpCode::EqualVerify,
294+
TransactionOpCode::CheckSigVerify,
295+
],
286296
}],
287297
locktime: 0,
288298
};
@@ -619,6 +629,7 @@ async fn main() -> anyhow::Result<()> {
619629
);
620630
let mut signing_key_decoded = [0u8; 32];
621631
hex::decode_to_slice(signing_key_hex, &mut signing_key_decoded)?;
632+
let signing_key = SigningKey::from_slice(&signing_key_decoded)?;
622633

623634
let p2p_port: u16 = env::var("QUIBLE_P2P_PORT")
624635
.unwrap_or_else(|_| "9014".to_owned())
@@ -686,7 +697,7 @@ async fn main() -> anyhow::Result<()> {
686697
_ => {}
687698
}
688699

689-
let result = propose_block(block_number, &db_arc).await;
700+
let result = propose_block(block_number, &db_arc, &signing_key).await;
690701

691702
if let Err(e) = result {
692703
eprintln!("Error in propose_block: {:#?}", e);
@@ -698,7 +709,7 @@ async fn main() -> anyhow::Result<()> {
698709
block_timestamp = block_timestamp + SLOT_DURATION;
699710
block_number += 1;
700711

701-
let result = propose_block(block_number, &db_arc).await;
712+
let result = propose_block(block_number, &db_arc, &signing_key).await;
702713

703714
if let Err(e) = result {
704715
eprintln!("Error in propose_block: {:#?}", e);
@@ -747,16 +758,17 @@ mod tests {
747758
use super::{db, run_derive_server};
748759
use crate::db::types::{BlockRow, ObjectRow, PendingTransactionRow};
749760
use crate::propose_block;
750-
use crate::quible_ecdsa_utils::recover_signer_unchecked;
761+
use crate::quible_ecdsa_utils::{recover_signer_unchecked, sign_message};
751762
use crate::rpc::QuibleRpcClient;
752763
use crate::tx::engine::compute_object_id;
753764
use crate::tx::types::{
754765
Hashable, ObjectIdentifier, ObjectMode, Transaction, TransactionInput, TransactionOpCode,
755766
TransactionOutpoint, TransactionOutput,
756767
};
757-
use alloy_primitives::Address;
768+
use alloy_primitives::{Address, B256};
758769
use anyhow::anyhow;
759770
use jsonrpsee::http_client::HttpClient;
771+
use k256::ecdsa::SigningKey;
760772
use std::sync::Arc;
761773
use surrealdb::engine::any;
762774

@@ -817,16 +829,15 @@ mod tests {
817829

818830
let db_arc = Arc::new(db);
819831

820-
let server_addr = run_derive_server(
821-
hex_literal::hex!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"),
822-
&db_arc,
823-
0,
824-
)
825-
.await?;
832+
let node_signing_key_bytes =
833+
hex_literal::hex!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80");
834+
let node_signing_key = SigningKey::from_slice(&node_signing_key_bytes)?;
835+
836+
let server_addr = run_derive_server(node_signing_key_bytes, &db_arc, 0).await?;
826837
let url = format!("http://{}", server_addr);
827838
println!("server listening at {}", url);
828839

829-
propose_block(1, &db_arc).await?;
840+
propose_block(1, &db_arc, &node_signing_key).await?;
830841

831842
// Query pending transactions from SurrealDB
832843
let block_rows: Vec<BlockRow> = db_arc.select("blocks").await?;
@@ -852,12 +863,11 @@ mod tests {
852863

853864
let db_arc = Arc::new(db);
854865

855-
let server_addr = run_derive_server(
856-
hex_literal::hex!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"),
857-
&db_arc,
858-
0,
859-
)
860-
.await?;
866+
let node_signing_key_bytes =
867+
hex_literal::hex!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80");
868+
let node_signing_key = SigningKey::from_slice(&node_signing_key_bytes)?;
869+
870+
let server_addr = run_derive_server(node_signing_key_bytes, &db_arc, 0).await?;
861871
let url = format!("http://{}", server_addr);
862872
println!("server listening at {}", url);
863873
let client = HttpClient::builder().build(url)?;
@@ -895,7 +905,7 @@ mod tests {
895905
.await
896906
.unwrap();
897907

898-
propose_block(1, &db_arc).await?;
908+
propose_block(1, &db_arc, &node_signing_key).await?;
899909

900910
// Query pending transactions from SurrealDB
901911
let block_rows: Vec<BlockRow> = db_arc.select("blocks").await?;
@@ -939,25 +949,24 @@ mod tests {
939949

940950
let db_arc = Arc::new(db);
941951

942-
let server_addr = run_derive_server(
943-
hex_literal::hex!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"),
944-
&db_arc,
945-
0,
946-
)
947-
.await?;
952+
let node_signing_key_bytes =
953+
hex_literal::hex!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80");
954+
let node_signing_key = SigningKey::from_slice(&node_signing_key_bytes)?;
955+
956+
let server_addr = run_derive_server(node_signing_key_bytes, &db_arc, 0).await?;
948957
let url = format!("http://{}", server_addr);
949958
println!("server listening at {}", url);
950959

951-
let block_row = propose_block(1, &db_arc).await?;
960+
let block_row = propose_block(1, &db_arc, &node_signing_key).await?;
952961

953962
let coinbase_transaction_hash = match &block_row.transactions[..] {
954963
[(hash, _)] => Ok(*hash),
955964
_ => Err(anyhow!("missing coinbase transaction")),
956965
}?;
957966

958967
let client = HttpClient::builder().build(url)?;
959-
// let signer_secret = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
960-
let sample_first_transaction = Transaction::Version1 {
968+
969+
let sample_first_transaction = &mut Transaction::Version1 {
961970
inputs: vec![TransactionInput {
962971
outpoint: TransactionOutpoint {
963972
txid: coinbase_transaction_hash,
@@ -972,12 +981,38 @@ mod tests {
972981
locktime: 0,
973982
};
974983

984+
let signature = sign_message(
985+
B256::from_slice(&node_signing_key_bytes),
986+
sample_first_transaction.hash()?.into(),
987+
)?
988+
.to_vec();
989+
990+
match sample_first_transaction {
991+
Transaction::Version1 { inputs, .. } => {
992+
for input in inputs.iter_mut() {
993+
*input = TransactionInput {
994+
outpoint: input.clone().outpoint,
995+
signature_script: vec![
996+
TransactionOpCode::Push {
997+
data: signature.clone(),
998+
},
999+
TransactionOpCode::Push {
1000+
data: Address::from_private_key(&node_signing_key)
1001+
.into_array()
1002+
.to_vec(),
1003+
},
1004+
],
1005+
}
1006+
}
1007+
}
1008+
}
1009+
9751010
client
9761011
.send_transaction(sample_first_transaction.clone())
9771012
.await
9781013
.unwrap();
9791014

980-
propose_block(2, &db_arc).await?;
1015+
propose_block(2, &db_arc, &node_signing_key).await?;
9811016

9821017
// Query pending transactions from SurrealDB
9831018
let mut result = db_arc
@@ -1018,12 +1053,11 @@ mod tests {
10181053

10191054
let db_arc = Arc::new(db);
10201055

1021-
let server_addr = run_derive_server(
1022-
hex_literal::hex!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"),
1023-
&db_arc,
1024-
0,
1025-
)
1026-
.await?;
1056+
let node_signing_key_bytes =
1057+
hex_literal::hex!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80");
1058+
let node_signing_key = SigningKey::from_slice(&node_signing_key_bytes)?;
1059+
1060+
let server_addr = run_derive_server(node_signing_key_bytes, &db_arc, 0).await?;
10271061
let url = format!("http://{}", server_addr);
10281062
println!("server listening at {}", url);
10291063
let client = HttpClient::builder().build(url)?;
@@ -1061,7 +1095,7 @@ mod tests {
10611095
.await
10621096
.unwrap();
10631097

1064-
propose_block(1, &db_arc).await?;
1098+
propose_block(1, &db_arc, &node_signing_key).await?;
10651099

10661100
// Query pending transactions from SurrealDB
10671101
let block_rows: Vec<BlockRow> = db_arc.select("blocks").await?;
@@ -1114,16 +1148,13 @@ mod tests {
11141148

11151149
let db_arc = Arc::new(db);
11161150

1117-
let server_signer_key = k256::ecdsa::SigningKey::from_slice(&hex_literal::hex!(
1118-
"ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
1119-
))?;
1151+
let server_signing_key_bytes =
1152+
hex_literal::hex!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80");
1153+
1154+
let server_signing_key = k256::ecdsa::SigningKey::from_slice(&server_signing_key_bytes)?;
1155+
1156+
let server_addr = run_derive_server(server_signing_key_bytes, &db_arc, 0).await?;
11201157

1121-
let server_addr = run_derive_server(
1122-
server_signer_key.to_bytes().as_slice().try_into()?,
1123-
&db_arc,
1124-
0,
1125-
)
1126-
.await?;
11271158
let url = format!("http://{}", server_addr);
11281159
println!("server listening at {}", url);
11291160
let client = HttpClient::builder().build(url)?;
@@ -1146,7 +1177,7 @@ mod tests {
11461177

11471178
client.send_transaction(sample_transaction.clone()).await?;
11481179

1149-
propose_block(1, &db_arc).await?;
1180+
propose_block(1, &db_arc, &server_signing_key).await?;
11501181

11511182
let cert = client
11521183
.request_certificate(object_id_raw, vec![1, 2, 3])
@@ -1156,7 +1187,7 @@ mod tests {
11561187
assert_eq!(cert.details.claim, vec![1, 2, 3]);
11571188
assert_eq!(
11581189
recover_signer_unchecked(&cert.signature.raw, &cert.details.hash()?)?,
1159-
Address::from_public_key(server_signer_key.verifying_key())
1190+
Address::from_private_key(&server_signing_key)
11601191
);
11611192

11621193
Ok(())
@@ -1171,12 +1202,12 @@ mod tests {
11711202

11721203
let db_arc = Arc::new(db);
11731204

1174-
let server_signer_key = k256::ecdsa::SigningKey::from_slice(&hex_literal::hex!(
1175-
"ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
1176-
))?;
1205+
let server_signing_key_bytes =
1206+
hex_literal::hex!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80");
1207+
let server_signing_key = k256::ecdsa::SigningKey::from_slice(&server_signing_key_bytes)?;
11771208

11781209
let server_addr = run_derive_server(
1179-
server_signer_key.to_bytes().as_slice().try_into()?,
1210+
server_signing_key.to_bytes().as_slice().try_into()?,
11801211
&db_arc,
11811212
0,
11821213
)
@@ -1203,7 +1234,7 @@ mod tests {
12031234

12041235
client.send_transaction(sample_transaction.clone()).await?;
12051236

1206-
propose_block(1, &db_arc).await?;
1237+
propose_block(1, &db_arc, &server_signing_key).await?;
12071238

12081239
let failure_response = client
12091240
.request_certificate(object_id_raw, vec![4, 5, 6])
@@ -1231,30 +1262,31 @@ mod tests {
12311262

12321263
let db_arc = Arc::new(db);
12331264

1234-
let server_signer_key = k256::ecdsa::SigningKey::from_slice(&hex_literal::hex!(
1235-
"ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
1236-
))?;
1265+
let server_signing_key_bytes =
1266+
hex_literal::hex!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80");
12371267

1238-
let server_addr = run_derive_server(
1239-
server_signer_key.to_bytes().as_slice().try_into()?,
1240-
&db_arc,
1241-
0,
1242-
)
1243-
.await?;
1268+
let server_signing_key = k256::ecdsa::SigningKey::from_slice(&server_signing_key_bytes)?;
1269+
let server_signing_key_bytes: [u8; 32] =
1270+
server_signing_key.to_bytes().as_slice().try_into()?;
1271+
1272+
let user_signing_key = k256::ecdsa::SigningKey::random(&mut rand::thread_rng());
1273+
1274+
let server_addr = run_derive_server(server_signing_key_bytes, &db_arc, 0).await?;
12441275
let url = format!("http://{}", server_addr);
12451276
println!("server listening at {}", url);
12461277
let client = HttpClient::builder().build(url)?;
12471278

1248-
let block_row = propose_block(1, &db_arc).await?;
1279+
let block_row = propose_block(1, &db_arc, &server_signing_key).await?;
12491280

12501281
let coinbase_transaction_hash = match &block_row.transactions[..] {
12511282
[(hash, _)] => Ok(*hash),
12521283
_ => Err(anyhow!("missing coinbase transaction")),
12531284
}?;
12541285

1255-
let owner_address = Address::from_private_key(&server_signer_key);
1286+
let owner_address = Address::from_private_key(&server_signing_key.clone());
1287+
let user_address = Address::from_private_key(&user_signing_key.clone());
12561288

1257-
let sample_transaction = Transaction::Version1 {
1289+
let sample_transaction = &mut Transaction::Version1 {
12581290
inputs: vec![TransactionInput {
12591291
outpoint: TransactionOutpoint {
12601292
txid: coinbase_transaction_hash,
@@ -1267,7 +1299,7 @@ mod tests {
12671299
pubkey_script: vec![
12681300
TransactionOpCode::Dup,
12691301
TransactionOpCode::Push {
1270-
data: owner_address.to_vec(),
1302+
data: user_address.to_vec(),
12711303
},
12721304
TransactionOpCode::EqualVerify,
12731305
TransactionOpCode::CheckSigVerify,
@@ -1276,12 +1308,36 @@ mod tests {
12761308
locktime: 0,
12771309
};
12781310

1311+
let signature = sign_message(
1312+
B256::from_slice(&server_signing_key_bytes),
1313+
sample_transaction.hash()?.into(),
1314+
)?
1315+
.to_vec();
1316+
1317+
match sample_transaction {
1318+
Transaction::Version1 { inputs, .. } => {
1319+
for input in inputs.iter_mut() {
1320+
*input = TransactionInput {
1321+
outpoint: input.clone().outpoint,
1322+
signature_script: vec![
1323+
TransactionOpCode::Push {
1324+
data: signature.clone(),
1325+
},
1326+
TransactionOpCode::Push {
1327+
data: owner_address.into_array().to_vec(),
1328+
},
1329+
],
1330+
}
1331+
}
1332+
}
1333+
}
1334+
12791335
client.send_transaction(sample_transaction.clone()).await?;
12801336

1281-
propose_block(2, &db_arc).await?;
1337+
propose_block(2, &db_arc, &server_signing_key).await?;
12821338

12831339
let payload = client
1284-
.fetch_unspent_value_outputs_by_owner(owner_address.into_array())
1340+
.fetch_unspent_value_outputs_by_owner(user_address.into_array())
12851341
.await?;
12861342

12871343
assert_eq!(payload.total_value, 5);

0 commit comments

Comments
 (0)