Add a basic multisig contract

This commit is contained in:
Pierre-Emmanuel Wulfman 2020-05-27 16:19:29 +02:00
parent c2e4f2f36d
commit 98487d8bb7
8 changed files with 430 additions and 0 deletions

View File

@ -0,0 +1,208 @@
open Trace
open Test_helpers
let file = "./contracts/basic_multisig/multisig.ligo"
let mfile = "./contracts/basic_multisig/multisig.mligo"
let refile = "./contracts/basic_multisig/multisig.religo"
let type_file f s =
let%bind typed,state = Ligo.Compile.Utils.type_file f s (Contract "main") in
ok @@ (typed,state)
let get_program f st =
let s = ref None in
fun () -> match !s with
| Some s -> ok s
| None -> (
let%bind program = type_file f st in
s := Some program ;
ok program
)
let compile_main f s () =
let%bind typed_prg,_ = type_file f s in
let%bind mini_c_prg = Ligo.Compile.Of_typed.compile typed_prg in
let%bind michelson_prg = Ligo.Compile.Of_mini_c.aggregate_and_compile_contract mini_c_prg "main" in
let%bind (_contract: Tezos_utils.Michelson.michelson) =
(* fails if the given entry point is not a valid contract *)
Ligo.Compile.Of_michelson.build_contract michelson_prg in
ok ()
open Ast_imperative
let init_storage threshold counter pkeys =
let keys = List.map
(fun el ->
let (_,pk_str,_) = str_keys el in
e_key @@ pk_str)
pkeys in
e_record_ez [
("id" , e_string "MULTISIG" ) ;
("counter" , e_nat counter ) ;
("threshold" , e_nat threshold) ;
("auth" , e_typed_list keys (t_key ())) ;
]
let (first_owner , first_contract) =
let open Proto_alpha_utils.Memory_proto_alpha in
let id = List.nth dummy_environment.identities 0 in
let kt = id.implicit_contract in
Protocol.Alpha_context.Contract.to_b58check kt , kt
let bad_contract () =
let title = (thunk ("Not a contract")) in
let message () = Format.asprintf "" in
let data = [
] in
error ~data title message ()
let op_list =
let open Memory_proto_alpha.Protocol.Alpha_context in
let source : Contract.t = first_contract in
let%bind operation =
let parameters : Script.lazy_expr = Script.unit_parameter in
let entrypoint = "default" in
let open Proto_alpha_utils in
let%bind destination = Trace.trace_alpha_tzresult (bad_contract) @@
Contract.of_b58check "tz1PpDGHRXFQq3sYDuH8EpLWzPm5PFpe1sLE"
in
ok @@ Transaction {amount=Tez.zero; parameters; entrypoint; destination} in
ok @@ (e_typed_list [e_literal (Literal_operation (Internal_operation {source;operation;nonce=0}))] (t_operation ()))
let empty_payload = e_unit ()
let chain_id_zero = e_chain_id @@ Tezos_crypto.Base58.simple_encode
Tezos_base__TzPervasives.Chain_id.b58check_encoding
Tezos_base__TzPervasives.Chain_id.zero
(* sign the message 'msg' with 'keys', if 'is_valid'=false the providid signature will be incorrect *)
let params counter payload keys is_validl f s =
let%bind program,_ = get_program f s () in
let aux = fun acc (key,is_valid) ->
let (_,_pk,sk) = key in
let (pkh,_,_) = str_keys key in
let msg = e_tuple
[ payload ;
e_nat counter ;
e_string (if is_valid then "MULTISIG" else "XX") ;
chain_id_zero ] in
let%bind signature = sign_message program msg sk in
ok @@ (e_pair (e_key_hash pkh) (e_signature signature))::acc in
let%bind signed_msgs = Trace.bind_fold_list aux [] (List.rev @@ List.combine keys is_validl) in
ok @@ e_record_ez [
("counter" , e_nat counter ) ;
("payload" , payload) ;
("signatures" , e_typed_list signed_msgs (t_pair (t_key_hash (),t_signature ())) ) ;
]
(* Provide one valid signature when the threshold is two of two keys *)
let not_enough_1_of_2 f s () =
let%bind program = get_program f s () in
let exp_failwith = "Not enough signatures passed the check" in
let keys = gen_keys () in
let%bind test_params = params 0 empty_payload [keys] [true] f s in
let options = Proto_alpha_utils.Memory_proto_alpha.make_options ~sender:first_contract () in
let%bind () = expect_string_failwith
program ~options "main" (e_pair test_params (init_storage 2 0 [keys;gen_keys()])) exp_failwith in
ok ()
let unmatching_counter f s () =
let%bind program = get_program f s () in
let exp_failwith = "Counters does not match" in
let keys = gen_keys () in
let%bind test_params = params 1 empty_payload [keys] [true] f s in
let%bind () = expect_string_failwith
program "main" (e_pair test_params (init_storage 1 0 [keys])) exp_failwith in
ok ()
(* Provide one invalid signature (correct key but incorrect signature)
when the threshold is one of one key *)
let invalid_1_of_1 f s () =
let%bind program = get_program f s () in
let exp_failwith = "Invalid signature" in
let keys = [gen_keys ()] in
let%bind test_params = params 0 empty_payload keys [false] f s in
let%bind () = expect_string_failwith
program "main" (e_pair test_params (init_storage 1 0 keys)) exp_failwith in
ok ()
(* Provide one valid signature when the threshold is one of one key *)
let valid_1_of_1 f s () =
let%bind program = get_program f s () in
let%bind op_list = op_list in
let keys = gen_keys () in
let%bind () = expect_eq_n_trace_aux [0;1;2] program "main"
(fun n ->
let%bind params = params n empty_payload [keys] [true] f s in
ok @@ e_pair params (init_storage 1 n [keys])
)
(fun n ->
ok @@ e_pair op_list (init_storage 1 (n+1) [keys])
) in
ok ()
(* Provive two valid signatures when the threshold is two of three keys *)
let valid_2_of_3 f s () =
let%bind program = get_program f s () in
let%bind op_list = op_list in
let param_keys = [gen_keys (); gen_keys ()] in
let st_keys = param_keys @ [gen_keys ()] in
let%bind () = expect_eq_n_trace_aux [0;1;2] program "main"
(fun n ->
let%bind params = params n empty_payload param_keys [true;true] f s in
ok @@ e_pair params (init_storage 2 n st_keys)
)
(fun n ->
ok @@ e_pair op_list (init_storage 2 (n+1) st_keys)
) in
ok ()
(* Provide one invalid signature and two valid signatures when the threshold is two of three keys *)
let invalid_3_of_3 f s () =
let%bind program = get_program f s () in
let valid_keys = [gen_keys() ; gen_keys()] in
let invalid_key = gen_keys () in
let param_keys = valid_keys @ [invalid_key] in
let st_keys = valid_keys @ [gen_keys ()] in
let%bind test_params = params 0 empty_payload param_keys [false;true;true] f s in
let exp_failwith = "Invalid signature" in
let%bind () = expect_string_failwith
program "main" (e_pair test_params (init_storage 2 0 st_keys)) exp_failwith in
ok ()
(* Provide two valid signatures when the threshold is three of three keys *)
let not_enough_2_of_3 f s () =
let%bind program = get_program f s() in
let valid_keys = [gen_keys() ; gen_keys()] in
let st_keys = gen_keys () :: valid_keys in
let%bind test_params = params 0 empty_payload (valid_keys) [true;true] f s in
let exp_failwith = "Not enough signatures passed the check" in
let%bind () = expect_string_failwith
program "main" (e_pair test_params (init_storage 3 0 st_keys)) exp_failwith in
ok ()
let main = test_suite "Basic Multisig" [
test "compile" (compile_main file "pascaligo");
test "unmatching_counter" (unmatching_counter file "pascaligo");
test "valid_1_of_1" (valid_1_of_1 file "pascaligo");
test "invalid_1_of_1" (invalid_1_of_1 file "pascaligo");
test "not_enough_signature" (not_enough_1_of_2 file "pascaligo");
test "valid_2_of_3" (valid_2_of_3 file "pascaligo");
test "invalid_3_of_3" (invalid_3_of_3 file "pascaligo");
test "not_enough_2_of_3" (not_enough_2_of_3 file "pascaligo");
test "compile (mligo)" (compile_main mfile "cameligo");
test "unmatching_counter (mligo)" (unmatching_counter mfile "cameligo");
test "valid_1_of_1 (mligo)" (valid_1_of_1 mfile "cameligo");
test "invalid_1_of_1 (mligo)" (invalid_1_of_1 mfile "cameligo");
test "not_enough_signature (mligo)" (not_enough_1_of_2 mfile "cameligo");
test "valid_2_of_3 (mligo)" (valid_2_of_3 mfile "cameligo");
test "invalid_3_of_3 (mligo)" (invalid_3_of_3 mfile "cameligo");
test "not_enough_2_of_3 (mligo)" (not_enough_2_of_3 mfile "cameligo");
test "compile (religo)" (compile_main refile "reasonligo");
test "unmatching_counter (religo)" (unmatching_counter refile "reasonligo");
test "valid_1_of_1 (religo)" (valid_1_of_1 refile "reasonligo");
test "invalid_1_of_1 (religo)" (invalid_1_of_1 refile "reasonligo");
test "not_enough_signature (religo)" (not_enough_1_of_2 refile "reasonligo");
test "valid_2_of_3 (religo)" (valid_2_of_3 refile "reasonligo");
test "invalid_3_of_3 (religo)" (invalid_3_of_3 refile "reasonligo");
test "not_enough_2_of_3 (religo)" (not_enough_2_of_3 refile "reasonligo");
]

View File

@ -0,0 +1,5 @@
type c_counter_type is nat
type c_payload_type is unit
const c_address : address =
("tz1PpDGHRXFQq3sYDuH8EpLWzPm5PFpe1sLE": address)

View File

@ -0,0 +1,5 @@
type c_counter_type = nat
type c_payload_type = unit
let c_address : address =
("tz1PpDGHRXFQq3sYDuH8EpLWzPm5PFpe1sLE": address)

View File

@ -0,0 +1,71 @@
#include "config.ligo"
// storage type
type counter is c_counter_type
type threshold is c_counter_type
type authorized_keys is list (key)
type storage is
record [
id : string;
counter : counter;
threshold : threshold;
auth : authorized_keys
]
// I/O types
type payload is c_payload_type
type signatures is list (key_hash * signature)
type parameter is
record [
counter : counter;
payload : payload;
signatures : signatures
]
type return is list (operation) * storage
function main (const p : parameter; const s : storage) : return is
block {
var payload: payload := p.payload;
if p.counter =/= s.counter then
failwith ("Counters does not match")
else {
const packed_payload : bytes =
Bytes.pack ((payload, p.counter, s.id, Tezos.chain_id));
var valid : nat := 0n;
var pkh_sigs : signatures := p.signatures;
for key in list s.auth block {
case pkh_sigs of
nil -> skip
| pkh_sig # tl -> block {
if pkh_sig.0 = Crypto.hash_key (key) then block {
pkh_sigs := tl;
if Crypto.check (key, pkh_sig.1, packed_payload)
then valid := valid + 1n
else failwith ("Invalid signature")
}
else skip
}
end
};
if valid < s.threshold then
failwith ("Not enough signatures passed the check")
else s.counter := s.counter + 1n
};
const contract_opt : option (contract(payload)) = Tezos.get_contract_opt(c_address);
var op : list(operation) := nil;
case contract_opt of
| Some (c) -> op := list [Tezos.transaction (payload, 0tez, c)]
| None -> failwith ("Contract not found")
end;
} with (op, s)

View File

@ -0,0 +1,65 @@
#include "config.mligo"
// storage type
type counter = c_counter_type
type threshold = c_counter_type
type authorized_keys = key list
type storage = {
id : string;
counter : counter;
threshold : threshold;
auth : authorized_keys
}
// I/O types
type payload = c_payload_type
type signatures = (key_hash * signature) list
type parameter = {
counter : counter;
payload : payload;
signatures : signatures
}
type return = operation list * storage
let main (p, s : parameter * storage) : return =
let payload : payload = p.payload in
let s =
if p.counter <> s.counter then
(failwith "Counters does not match" : storage)
else
let packed_payload : bytes =
Bytes.pack (payload, p.counter, s.id, Tezos.chain_id) in
let valid : nat = 0n in
let keys : authorized_keys = s.auth in
let aux =
fun (vk, pkh_sig: (nat * authorized_keys)*(key_hash * signature)) ->
let valid, keys = vk in
match keys with
| [] -> vk
| key::keys ->
if pkh_sig.0 = Crypto.hash_key key
then
let valid =
if Crypto.check key pkh_sig.1 packed_payload
then valid + 1n
else (failwith "Invalid signature" : nat)
in valid, keys
else valid, keys in
let valid, keys =
List.fold aux p.signatures (valid, keys) in
if valid < s.threshold then
(failwith ("Not enough signatures passed the check") : storage)
else {s with counter = s.counter + 1n}
in
let contract_opt : payload contract option = Tezos.get_contract_opt(c_address) in
let op = match contract_opt with
Some (c) -> [Tezos.transaction payload 0tez c]
| None -> (failwith ("Contract not found") : operation list)
in
op, s

View File

@ -0,0 +1,74 @@
#include "config.mligo"
// storage type
type counter = c_counter_type
type threshold = c_counter_type
type authorized_keys = list (key);
type storage = {
id : string,
counter : counter,
threshold : threshold,
auth : authorized_keys
};
// I/O types
type payload = c_payload_type
type dummy = (key_hash,signature);
type signatures = list ((key_hash,signature)); /* Waiting to be fixed */
type parameter = {
counter : counter,
payload : payload,
signatures : signatures
};
type return = (list (operation),storage);
let main = ((p, s): (parameter, storage)) : return =>
{
let payload : payload = p.payload;
let s =
if (p.counter != s.counter) {
(failwith ("Counters does not match") : storage);
} else {
let packed_payload : bytes =
Bytes.pack ((payload, p.counter, s.id, Tezos.chain_id));
let valid : nat = 0n;
let keys : authorized_keys = s.auth;
let aux = ((vk, pkh_sig) :
((nat, authorized_keys), (key_hash, signature)))
: (nat, authorized_keys) => {
let (valid, keys) = vk;
switch (keys) {
| [] => vk;
| [key, ...keys] =>
if (pkh_sig[0] == Crypto.hash_key (key)) {
let valid =
if (Crypto.check (key, pkh_sig[1], packed_payload)) {
valid + 1n;
}
else { (failwith ("Invalid signature") : nat) };
(valid, keys);
}
else { (valid, keys); };
};
};
let (valid, keys) =
List.fold (aux, p.signatures, (valid, keys));
if (valid < s.threshold) {
(failwith ("Not enough signatures passed the check") : storage);
}
else {
{...s,counter : s.counter + 1n};
};
};
let contract_opt : option (contract (payload)) = Tezos.get_contract_opt(c_address);
let op = switch (contract_opt) {
| Some (c) => [Tezos.transaction(payload, 0tez, c)]
| None => (failwith ("Contract not found") : list (operation))
};
(op,s)
};

View File

@ -6,6 +6,7 @@
simple-utils simple-utils
ligo ligo
alcotest alcotest
tezos-utils
tezos-crypto tezos-crypto
) )
(preprocess (preprocess

View File

@ -13,6 +13,7 @@ let () =
Id_tests.main ; Id_tests.main ;
Id_tests_p.main ; Id_tests_p.main ;
Id_tests_r.main ; Id_tests_r.main ;
Basic_multisig_tests.main;
Multisig_tests.main ; Multisig_tests.main ;
Multisig_v2_tests.main ; Multisig_v2_tests.main ;
Replaceable_id_tests.main ; Replaceable_id_tests.main ;