Skip to content

Commit ecbb4bb

Browse files
CopilotSteake
andauthored
Implement real transaction system for wallet GUI and RPC (#87)
* Initial plan * Add signing_hash method and wallet GUI transaction implementation Co-authored-by: Steake <530040+Steake@users.noreply.github.com> * Add transaction signing tests and fix Signature initialization Co-authored-by: Steake <530040+Steake@users.noreply.github.com> * Add PLACEHOLDER_SIGNATURE constants and improve code clarity Co-authored-by: Steake <530040+Steake@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Steake <530040+Steake@users.noreply.github.com>
1 parent a809d8f commit ecbb4bb

1 file changed

Lines changed: 73 additions & 1 deletion

File tree

crates/bitcell-consensus/src/block.rs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ pub struct Transaction {
114114
}
115115

116116
impl Transaction {
117-
/// Compute transaction hash
117+
/// Compute transaction hash (includes signature for uniqueness)
118118
pub fn hash(&self) -> Hash256 {
119119
// Note: bincode serialization to Vec cannot fail for this structure
120120
let serialized = bincode::serialize(self).expect("transaction serialization should never fail");
@@ -160,6 +160,9 @@ mod tests {
160160
use super::*;
161161
use bitcell_crypto::SecretKey;
162162

163+
/// Placeholder signature for tests (before actual signing)
164+
const PLACEHOLDER_SIGNATURE: [u8; 64] = [0u8; 64];
165+
163166
#[test]
164167
fn test_block_header_hash() {
165168
let sk = SecretKey::generate();
@@ -197,4 +200,73 @@ mod tests {
197200
let hash = tx.hash();
198201
assert_ne!(hash, Hash256::zero());
199202
}
203+
204+
#[test]
205+
fn test_transaction_signing_hash() {
206+
let sk = SecretKey::generate();
207+
let pk = sk.public_key();
208+
209+
// Create transaction with placeholder signature (will be replaced after signing)
210+
let placeholder_sig = bitcell_crypto::Signature::from_bytes(PLACEHOLDER_SIGNATURE);
211+
let mut tx = Transaction {
212+
nonce: 1,
213+
from: pk.clone(),
214+
to: pk.clone(),
215+
amount: 100,
216+
gas_limit: 21000,
217+
gas_price: 1000,
218+
data: vec![],
219+
signature: placeholder_sig,
220+
};
221+
222+
// Get signing hash and sign
223+
let signing_hash = tx.signing_hash();
224+
let signature = sk.sign(signing_hash.as_bytes());
225+
tx.signature = signature;
226+
227+
// Verify signature using signing_hash (not full hash)
228+
assert!(tx.signature.verify(&pk, signing_hash.as_bytes()).is_ok());
229+
230+
// The full hash should be different from signing hash (because it includes signature)
231+
let full_hash = tx.hash();
232+
assert_ne!(full_hash, signing_hash);
233+
}
234+
235+
#[test]
236+
fn test_signing_hash_excludes_signature() {
237+
let sk = SecretKey::generate();
238+
let pk = sk.public_key();
239+
240+
// Create two identical transactions with different signatures
241+
let sig1 = sk.sign(b"different1");
242+
let sig2 = sk.sign(b"different2");
243+
244+
let tx1 = Transaction {
245+
nonce: 1,
246+
from: pk.clone(),
247+
to: pk.clone(),
248+
amount: 100,
249+
gas_limit: 21000,
250+
gas_price: 1000,
251+
data: vec![],
252+
signature: sig1,
253+
};
254+
255+
let tx2 = Transaction {
256+
nonce: 1,
257+
from: pk.clone(),
258+
to: pk.clone(),
259+
amount: 100,
260+
gas_limit: 21000,
261+
gas_price: 1000,
262+
data: vec![],
263+
signature: sig2,
264+
};
265+
266+
// Signing hashes should be identical (signature not included)
267+
assert_eq!(tx1.signing_hash(), tx2.signing_hash());
268+
269+
// Full hashes should be different (signature included)
270+
assert_ne!(tx1.hash(), tx2.hash());
271+
}
200272
}

0 commit comments

Comments
 (0)