Skip to content

Commit 702186d

Browse files
committed
feat: scriptful 0.4
feat(stack): implement human friendly parser format remove MyValue::Signature type add tx_hash to script context feat(cli): add decodeScript command and improve other scripting commands Add script encode command, allow decode script from file Add documentation to script::parse Add commands to spendScriptUtxo and addTxWitness Add command to calculate script address Fix stack tests by disabling signature validation TODO: But we use real public keys and signatures so we could enable it...
1 parent a32492f commit 702186d

File tree

12 files changed

+1532
-1130
lines changed

12 files changed

+1532
-1130
lines changed

Cargo.lock

+6-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

node/src/actors/chain_manager/handlers.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1306,7 +1306,7 @@ impl Handler<BuildScriptTransaction> for ChainManager {
13061306
// Script transactions are not signed by this method because the witness may need
13071307
// something more aside from a single signature, so script transactions need to be
13081308
// manually signed using other methods.
1309-
let empty_witness = witnet_stack::encode(vec![]).unwrap();
1309+
let empty_witness = witnet_stack::encode(&[]).unwrap();
13101310
let num_inputs = vtt.inputs.len();
13111311
let transaction = Transaction::ValueTransfer(VTTransaction {
13121312
body: vtt,

node/src/actors/chain_manager/mod.rs

+2-8
Original file line numberDiff line numberDiff line change
@@ -3721,21 +3721,15 @@ mod tests {
37213721
.insert(out_ptr, vto1, 0);
37223722
chain_manager.chain_state.reputation_engine = Some(ReputationEngine::new(1000));
37233723
chain_manager.vrf_ctx = Some(VrfCtx::secp256k1().unwrap());
3724-
chain_manager.secp = Some(Secp256k1::new());
37253724
chain_manager.sm_state = StateMachine::Synced;
37263725

37273726
let t1 = create_valid_transaction(&mut chain_manager, &PRIV_KEY_1);
37283727
let mut t1_mal = t1.clone();
37293728
// Malleability!
37303729
match &mut t1_mal {
37313730
Transaction::ValueTransfer(vtt) => {
3732-
// Invalidate signature
3733-
match &mut vtt.signatures[0].signature {
3734-
Signature::Secp256k1(secp_sig) => {
3735-
// Flip 1 bit
3736-
secp_sig.der[10] ^= 0x01;
3737-
}
3738-
}
3731+
// Invalidate signature by flipping 1 bit
3732+
vtt.witness[0][10] ^= 0x01;
37393733
}
37403734
_ => {
37413735
panic!(

src/cli/node/json_rpc_client.rs

+168-17
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use ansi_term::Color::{Purple, Red, White, Yellow};
2-
use failure::{bail, Fail};
2+
use failure::{bail, format_err, Fail};
33
use itertools::Itertools;
44
use num_format::{Locale, ToFormattedString};
55
use prettytable::{cell, row, Table};
@@ -711,7 +711,6 @@ pub fn master_key_export(
711711
Ok(())
712712
}
713713

714-
#[allow(clippy::too_many_arguments)]
715714
pub fn create_multisig_address(
716715
n: u8,
717716
m: u8,
@@ -736,12 +735,11 @@ pub fn create_multisig_address(
736735
Item::Operator(MyOperator::CheckMultiSig),
737736
]);
738737

739-
let locking_script_hash =
740-
PublicKeyHash::from_script_bytes(&witnet_stack::encode(redeem_script)?);
738+
let script_address = PublicKeyHash::from_script_bytes(&witnet_stack::encode(&redeem_script)?);
741739

742740
println!(
743-
"Sending to {}-of-{} multisig address {} composed of {:?}",
744-
m, n, locking_script_hash, pkhs_str
741+
"Created {}-of-{} multisig address {} composed of {:?}",
742+
m, n, script_address, pkhs_str
745743
);
746744

747745
Ok(())
@@ -778,7 +776,7 @@ pub fn create_opened_multisig(
778776
Item::Value(MyValue::Integer(i128::from(n))),
779777
Item::Operator(MyOperator::CheckMultiSig),
780778
]);
781-
let redeem_script_bytes = witnet_stack::encode(redeem_script)?;
779+
let redeem_script_bytes = witnet_stack::encode(&redeem_script)?;
782780
let vt_outputs = vec![ValueTransferOutput {
783781
pkh: address,
784782
value,
@@ -807,7 +805,53 @@ pub fn create_opened_multisig(
807805
let script_tx = parse_response::<Transaction>(&response)?;
808806
println!("Created transaction:\n{:?}", script_tx);
809807
let script_transaction_hex = hex::encode(script_tx.to_pb_bytes().unwrap());
810-
println!("Script bytes: {}", script_transaction_hex);
808+
println!("Transaction bytes: {}", script_transaction_hex);
809+
}
810+
Ok(())
811+
}
812+
813+
#[allow(clippy::too_many_arguments)]
814+
pub fn spend_script_utxo(
815+
addr: SocketAddr,
816+
output_pointer: OutputPointer,
817+
value: u64,
818+
fee: u64,
819+
hex: String,
820+
change_address: Option<PublicKeyHash>,
821+
address: PublicKeyHash,
822+
dry_run: bool,
823+
) -> Result<(), failure::Error> {
824+
let mut stream = start_client(addr)?;
825+
let redeem_script_bytes = hex::decode(hex)?;
826+
let vt_outputs = vec![ValueTransferOutput {
827+
pkh: address,
828+
value,
829+
time_lock: 0,
830+
}];
831+
let utxo_strategy = UtxoSelectionStrategy::Random { from: None };
832+
let script_inputs = vec![Input {
833+
output_pointer,
834+
redeem_script: redeem_script_bytes,
835+
}];
836+
let params = BuildScriptTransaction {
837+
vto: vt_outputs,
838+
fee,
839+
utxo_strategy,
840+
script_inputs,
841+
change_address,
842+
};
843+
let request = format!(
844+
r#"{{"jsonrpc": "2.0","method": "sendScript", "params": {}, "id": "1"}}"#,
845+
serde_json::to_string(&params)?
846+
);
847+
if dry_run {
848+
println!("{}", request);
849+
} else {
850+
let response = send_request(&mut stream, &request)?;
851+
let script_tx = parse_response::<Transaction>(&response)?;
852+
println!("Created transaction:\n{:?}", script_tx);
853+
let script_transaction_hex = hex::encode(script_tx.to_pb_bytes().unwrap());
854+
println!("Transaction bytes: {}", script_transaction_hex);
811855
}
812856
Ok(())
813857
}
@@ -832,7 +876,13 @@ pub fn broadcast_tx(addr: SocketAddr, hex: String, dry_run: bool) -> Result<(),
832876
Ok(())
833877
}
834878

835-
pub fn sign_tx(addr: SocketAddr, hex: String, dry_run: bool) -> Result<(), failure::Error> {
879+
pub fn sign_tx(
880+
addr: SocketAddr,
881+
hex: String,
882+
input_index: usize,
883+
signature_position_in_witness: usize,
884+
dry_run: bool,
885+
) -> Result<(), failure::Error> {
836886
let mut stream = start_client(addr)?;
837887

838888
let mut tx: Transaction = Transaction::from_pb_bytes(&hex::decode(hex)?)?;
@@ -854,21 +904,122 @@ pub fn sign_tx(addr: SocketAddr, hex: String, dry_run: bool) -> Result<(), failu
854904
match tx {
855905
Transaction::ValueTransfer(ref mut vtt) => {
856906
let signature_bytes = signature.to_pb_bytes()?;
857-
let mut script = witnet_stack::decode(&vtt.witness[0])?;
907+
// TODO: this only works if the witness field represents a script
908+
// It could also represent a signature in the case of normal value transfer
909+
// transactions. It would be nice to also support signing normal transactions here
910+
let mut script = witnet_stack::decode(&vtt.witness[input_index])?;
911+
912+
println!(
913+
"-----------------------\nPrevious witness:\n-----------------------\n{}",
914+
witnet_stack::parser::script_to_string(&script)
915+
);
858916

859-
println!("Previous script:\n{:?}", script);
860-
script.push(Item::Value(MyValue::Signature(signature_bytes)));
917+
script.insert(
918+
signature_position_in_witness,
919+
Item::Value(MyValue::Bytes(signature_bytes)),
920+
);
861921

862-
println!("Post script:\n{:?}", script);
863-
let encoded_script = witnet_stack::encode(script)?;
922+
println!(
923+
"-----------------------\nNew witness:\n-----------------------\n{}",
924+
witnet_stack::parser::script_to_string(&script)
925+
);
926+
let encoded_script = witnet_stack::encode(&script)?;
864927

865-
vtt.witness[0] = encoded_script;
928+
vtt.witness[input_index] = encoded_script;
866929

867930
let script_transaction_hex = hex::encode(tx.to_pb_bytes().unwrap());
868931
println!("Signed Transaction:\n{:?}", tx);
869-
println!("Script bytes: {}", script_transaction_hex);
932+
println!("Signed Transaction hex bytes: {}", script_transaction_hex);
870933
}
871-
_ => unimplemented!("We only can sign ScriptTransactions"),
934+
_ => unimplemented!("We only can sign ValueTransfer transactions"),
935+
}
936+
}
937+
938+
Ok(())
939+
}
940+
941+
pub fn add_tx_witness(
942+
_addr: SocketAddr,
943+
hex: String,
944+
witness: String,
945+
input_index: usize,
946+
) -> Result<(), failure::Error> {
947+
let mut tx: Transaction = Transaction::from_pb_bytes(&hex::decode(hex)?)?;
948+
949+
println!("Transaction to sign is:\n{:?}", tx);
950+
951+
match tx {
952+
Transaction::ValueTransfer(ref mut vtt) => {
953+
let encoded_script = hex::decode(witness)?;
954+
vtt.witness[input_index] = encoded_script;
955+
956+
let script_transaction_hex = hex::encode(tx.to_pb_bytes().unwrap());
957+
println!("Signed Transaction:\n{:?}", tx);
958+
println!("Signed Transaction hex bytes: {}", script_transaction_hex);
959+
}
960+
_ => unimplemented!("We only can sign ValueTransfer transactions"),
961+
}
962+
963+
Ok(())
964+
}
965+
966+
pub fn script_address(hex: String) -> Result<(), failure::Error> {
967+
let script_bytes = hex::decode(hex)?;
968+
let script_pkh = PublicKeyHash::from_script_bytes(&script_bytes);
969+
println!(
970+
"Script address (mainnet): {}",
971+
script_pkh.bech32(Environment::Mainnet)
972+
);
973+
println!(
974+
"Script address (testnet): {}",
975+
script_pkh.bech32(Environment::Testnet)
976+
);
977+
978+
Ok(())
979+
}
980+
981+
pub fn address_to_bytes(address: PublicKeyHash) -> Result<(), failure::Error> {
982+
let hex_address = hex::encode(address.as_ref());
983+
println!("{}", hex_address);
984+
985+
Ok(())
986+
}
987+
988+
/// Convert script text file into hex bytes
989+
pub fn encode_script(script_file: &Path) -> Result<(), failure::Error> {
990+
let script_str = std::fs::read_to_string(script_file)?;
991+
let script = witnet_stack::parser::parse_script(&script_str)
992+
.map_err(|e| format_err!("Failed to parse script: {:?}", e))?;
993+
let script_bytes = witnet_stack::encode(&script)?;
994+
let script_hex = hex::encode(&script_bytes);
995+
let script_pkh = PublicKeyHash::from_script_bytes(&script_bytes);
996+
println!("Script address: {}", script_pkh);
997+
998+
println!("{}", script_hex);
999+
1000+
Ok(())
1001+
}
1002+
1003+
/// Convert hex bytes into script text, and optionally write to file
1004+
pub fn decode_script(hex: String, script_file: Option<&Path>) -> Result<(), failure::Error> {
1005+
let script_bytes = hex::decode(hex)?;
1006+
let script_pkh = PublicKeyHash::from_script_bytes(&script_bytes);
1007+
println!("Script address: {}", script_pkh);
1008+
1009+
let script = witnet_stack::decode(&script_bytes)?;
1010+
1011+
let script_str = witnet_stack::parser::script_to_string(&script);
1012+
1013+
match script_file {
1014+
Some(script_file) => {
1015+
std::fs::write(script_file, script_str)?;
1016+
println!(
1017+
"Script written to {}",
1018+
script_file.canonicalize()?.as_path().display()
1019+
);
1020+
}
1021+
None => {
1022+
println!("{}", script_str);
8721023
}
8731024
}
8741025

0 commit comments

Comments
 (0)