kaspanet / rusty-kaspa

Kaspa full-node and related libraries in the Rust programming language. This is a Beta version at the final testing phases.
ISC License
350 stars 105 forks source link

How to construct a partially signed transaction #463

Open Ligang9 opened 3 weeks ago

Ligang9 commented 3 weeks ago

Can you give me a demo of a transaction involving partial signatures from multiple people? In addition, I have a question: For example, a transaction requires three people to sign. If the first two people have signed, and it is the third person's turn to sign, will the third person see the private keys of the first two people?

aspect commented 3 weeks ago

You only see signatures from other parties. Private keys never get exposed.

Which language/framework are you using (direct Rust or JavaScript/TypeScript)?

Ligang9 commented 3 weeks ago

The language I use is JavaScript/TypeScript, thank you for your reply

Ligang9 commented 3 weeks ago

I still have a question. For example, a transaction requires three people to sign. Now the first two people have signed, and now it is the third person's turn to sign. Does privkeys need to pass in the private keys of the three people, or does it mean that you only need to pass in your own private key? That's it

pub fn sign_with_multiple_v2(mut mutable_tx: SignableTransaction, privkeys: Vec<[u8; 32]>) -> Signed { let mut map = BTreeMap::new(); for privkey in privkeys { let schnorr_key = secp256k1::KeyPair::from_seckey_slice(secp256k1::SECP256K1, &privkey).unwrap(); let schnorr_public_key = schnorr_key.public_key().x_only_public_key().0; let script_pub_key_script = once(0x20).chain(schnorr_public_key.serialize().into_iter()).chain(once(0xac)).collect_vec(); map.insert(script_pub_key_script, schnorr_key); }

let mut reused_values = SigHashReusedValues::new();
let mut additional_signatures_required = false;
for i in 0..mutable_tx.tx.inputs.len() {
    let script = mutable_tx.entries[i].as_ref().unwrap().script_public_key.script();
    if let Some(schnorr_key) = map.get(&script.to_vec()) {
        let sig_hash = calc_schnorr_signature_hash(&mutable_tx.as_verifiable(), i, SIG_HASH_ALL, &mut reused_values);
        let msg = secp256k1::Message::from_slice(sig_hash.as_bytes().as_slice()).unwrap();
        let sig: [u8; 64] = *schnorr_key.sign_schnorr(msg).as_ref();
        // This represents OP_DATA_65 <SIGNATURE+SIGHASH_TYPE> (since signature length is 64 bytes and SIGHASH_TYPE is one byte)
        mutable_tx.tx.inputs[i].signature_script = std::iter::once(65u8).chain(sig).chain([SIG_HASH_ALL.to_u8()]).collect();
    } else {
        additional_signatures_required = true;
    }
}
if additional_signatures_required {
    Signed::Partially(mutable_tx)
} else {
    Signed::Fully(mutable_tx)
}

}

aspect commented 3 weeks ago

Passing private keys around would defeat the purpose of any multi-party signing.

https://kaspa.aspectron.org/docs/classes/Transaction.html has serializeToJSON and serializeToSafeJSON (and the opposites) which allow ser/deser of transactions to/from JSON. "SafeJSON" converts all bigint values to strings; there is also serializeToObject that returns a pure JavaScript Object.

You should ask for feedback to your questions on Discord#development