diff --git a/src/proto/alpha/TEZOS_PROTOCOL b/src/proto/alpha/TEZOS_PROTOCOL index 6064904f2..21c25a9b6 100644 --- a/src/proto/alpha/TEZOS_PROTOCOL +++ b/src/proto/alpha/TEZOS_PROTOCOL @@ -23,6 +23,7 @@ "Roll_repr", "Vote_repr", "Operation_repr", + "Manager_repr", "Block_header_repr", "Storage_sigs", diff --git a/src/proto/alpha/apply.ml b/src/proto/alpha/apply.ml index c4f2856a7..9933e97da 100644 --- a/src/proto/alpha/apply.ml +++ b/src/proto/alpha/apply.ml @@ -149,28 +149,15 @@ let apply_manager_operation_content Contract.set_delegate ctxt source delegate >>=? fun ctxt -> return (ctxt, origination_nonce, None) -let check_signature_and_update_public_key ctxt id public_key op = - begin - match public_key with - | None -> return ctxt - | Some public_key -> - Public_key.reveal ctxt id public_key - end >>=? fun ctxt -> - Public_key.get ctxt id >>=? fun public_key -> - Operation.check_signature public_key op >>=? fun () -> - return ctxt - let apply_sourced_operation ctxt baker_contract pred_block block_prio operation origination_nonce ops = match ops with | Manager_operations { source ; public_key ; fee ; counter ; operations = contents } -> Contract.must_exist ctxt source >>=? fun () -> - Contract.get_manager ctxt source >>=? fun manager -> - check_signature_and_update_public_key - ctxt manager public_key operation >>=? fun ctxt -> - Contract.check_counter_increment - ctxt source counter >>=? fun () -> + Contract.update_manager_key ctxt source public_key >>=? fun (ctxt,public_key) -> + Operation.check_signature public_key operation >>=? fun () -> + Contract.check_counter_increment ctxt source counter >>=? fun () -> Contract.increment_counter ctxt source >>=? fun ctxt -> Contract.spend ctxt source fee >>=? fun ctxt -> (match baker_contract with @@ -187,10 +174,10 @@ let apply_sourced_operation (ctxt, origination_nonce, None) contents | Delegate_operations { source ; operations = contents } -> let delegate = Ed25519.Public_key.hash source in - check_signature_and_update_public_key - ctxt delegate (Some source) operation >>=? fun ctxt -> + Public_key.reveal ctxt delegate source >>=? fun ctxt -> + Operation.check_signature source operation >>=? fun () -> (* TODO, see how to extract the public key hash after this operation to - pass it to apply_delegate_operation_content *) + pass it to apply_delegate_operation_content *) fold_left_s (fun ctxt content -> apply_delegate_operation_content ctxt delegate pred_block block_prio content) diff --git a/src/proto/alpha/contract_storage.ml b/src/proto/alpha/contract_storage.ml index c3603b5b9..f530826c7 100644 --- a/src/proto/alpha/contract_storage.ml +++ b/src/proto/alpha/contract_storage.ml @@ -16,6 +16,9 @@ type error += | Unspendable_contract of Contract_repr.contract (* `Permanent *) | Non_existing_contract of Contract_repr.contract (* `Temporary *) | Non_delegatable_contract of Contract_repr.contract (* `Permanent *) + | Inconsistent_hash of Ed25519.Public_key.t * Ed25519.Public_key_hash.t * Ed25519.Public_key_hash.t (* `Permanent *) + | Inconsistent_public_key of Ed25519.Public_key.t * Ed25519.Public_key.t (* `Permanent *) + | Missing_public_key of Ed25519.Public_key_hash.t (* `Permanent *) | Failure of string (* `Permanent *) let () = @@ -129,6 +132,47 @@ let () = Data_encoding.(obj1 (req "contract" Contract_repr.encoding)) (function Non_delegatable_contract c -> Some c | _ -> None) (fun c -> Non_delegatable_contract c) ; + register_error_kind + `Permanent + ~id:"contract.manager.inconsistent_hash" + ~title:"Inconsistent public key hash" + ~description:"A revealed manager public key is inconsistent with the announced hash" + ~pp:(fun ppf (k, eh, ph) -> + Format.fprintf ppf "The hash of the manager public key %s is not %a as announced but %a" + (Ed25519.Public_key.to_b58check k) + Ed25519.Public_key_hash.pp ph + Ed25519.Public_key_hash.pp eh) + Data_encoding.(obj3 + (req "public_key" Ed25519.Public_key.encoding) + (req "expected_hash" Ed25519.Public_key_hash.encoding) + (req "provided_hash" Ed25519.Public_key_hash.encoding)) + (function Inconsistent_hash (k, eh, ph) -> Some (k, eh, ph) | _ -> None) + (fun (k, eh, ph) -> Inconsistent_hash (k, eh, ph)) ; + register_error_kind + `Permanent + ~id:"contract.manager.inconsistent_public_key" + ~title:"Inconsistent public key" + ~description:"A provided manager public key is different with the public key stored in the contract" + ~pp:(fun ppf (eh, ph) -> + Format.fprintf ppf "Expected manager public key %s but %s was provided" + (Ed25519.Public_key.to_b58check ph) + (Ed25519.Public_key.to_b58check eh)) + Data_encoding.(obj2 + (req "public_key" Ed25519.Public_key.encoding) + (req "expected_public_key" Ed25519.Public_key.encoding)) + (function Inconsistent_public_key (eh, ph) -> Some (eh, ph) | _ -> None) + (fun (eh, ph) -> Inconsistent_public_key (eh, ph)) ; + register_error_kind + `Permanent + ~id:"contract.manager.missing_public_key" + ~title:"Missing public key" + ~description:"The manager public key must be provided to execute the current operation" + ~pp:(fun ppf (k) -> + Format.fprintf ppf "The manager public key ( with hash %a ) is missing" + Ed25519.Public_key_hash.pp k) + Data_encoding.(obj1 (req "hash" Ed25519.Public_key_hash.encoding)) + (function Missing_public_key (k) -> Some (k) | _ -> None) + (fun (k) -> Missing_public_key (k)) ; register_error_kind `Permanent ~id:"contract.failure" @@ -138,7 +182,7 @@ let () = Data_encoding.(obj1 (req "message" string)) (function Failure s -> Some s | _ -> None) (fun s -> Failure s) - + let failwith msg = fail (Failure msg) let create_base c contract ~balance ~manager ~delegate ?script ~spendable ~delegatable = @@ -146,7 +190,7 @@ let create_base c contract ~balance ~manager ~delegate ?script ~spendable ~deleg | None -> return 0l | Some _ -> Storage.Contract.Global_counter.get c) >>=? fun counter -> Storage.Contract.Balance.init c contract balance >>=? fun c -> - Storage.Contract.Manager.init c contract manager >>=? fun c -> + Storage.Contract.Manager.init c contract (Manager_repr.hash manager) >>=? fun c -> begin match delegate with | None -> return c @@ -254,7 +298,28 @@ let get_manager c contract = | Some manager -> return manager | None -> failwith "get_manager" end - | Some v -> return v + | Some (Manager_repr.Hash v) -> return v + | Some (Manager_repr.Public_key v) -> return (Ed25519.Public_key.hash v) + +let update_manager_key c contract = function + | Some public_key -> + begin Storage.Contract.Manager.get c contract >>=? function + | (Manager_repr.Public_key v) -> (* key revealed for the second time *) + if Ed25519.Public_key.(v = public_key) then return (c,v) + else fail (Inconsistent_public_key (v,public_key)) + | (Manager_repr.Hash v) -> + let actual_hash = Ed25519.Public_key.hash public_key in + if (Ed25519.Public_key_hash.equal actual_hash v) then + let v = (Manager_repr.public_key public_key) in + Storage.Contract.Manager.set c contract v >>=? fun c -> + return (c,public_key) (* reveal and update key *) + else fail (Inconsistent_hash (public_key,v,actual_hash)) + end + | None -> + begin Storage.Contract.Manager.get c contract >>=? function + | (Manager_repr.Public_key v) -> return (c,v) (* already revealed *) + | (Manager_repr.Hash v) -> fail (Missing_public_key (v)) + end let get_delegate_opt = Roll_storage.get_contract_delegate diff --git a/src/proto/alpha/contract_storage.mli b/src/proto/alpha/contract_storage.mli index 15609fb1d..465bc79e5 100644 --- a/src/proto/alpha/contract_storage.mli +++ b/src/proto/alpha/contract_storage.mli @@ -16,6 +16,9 @@ type error += | Unspendable_contract of Contract_repr.contract (* `Permanent *) | Non_existing_contract of Contract_repr.contract (* `Temporary *) | Non_delegatable_contract of Contract_repr.contract (* `Permanent *) + | Inconsistent_hash of Ed25519.Public_key.t * Ed25519.Public_key_hash.t * Ed25519.Public_key_hash.t (* `Permanent *) + | Inconsistent_public_key of Ed25519.Public_key.t * Ed25519.Public_key.t (* `Permanent *) + | Missing_public_key of Ed25519.Public_key_hash.t (* `Permanent *) | Failure of string (* `Permanent *) val delete : Storage.t -> Contract_repr.t -> Storage.t tzresult Lwt.t @@ -32,6 +35,10 @@ val is_delegatable : Storage.t -> Contract_repr.t -> bool tzresult Lwt.t val is_spendable : Storage.t -> Contract_repr.t -> bool tzresult Lwt.t val get_manager: Storage.t -> Contract_repr.t -> Ed25519.Public_key_hash.t tzresult Lwt.t +val update_manager_key: + Storage.t -> Contract_repr.t -> Ed25519.Public_key.t option -> + (Storage.t * Ed25519.Public_key.t) tzresult Lwt.t + val get_delegate_opt: Storage.t -> Contract_repr.t -> Ed25519.Public_key_hash.t option tzresult Lwt.t val get_balance: Storage.t -> Contract_repr.t -> Tez_repr.t tzresult Lwt.t val get_counter: Storage.t -> Contract_repr.t -> int32 tzresult Lwt.t diff --git a/src/proto/alpha/manager_repr.ml b/src/proto/alpha/manager_repr.ml new file mode 100644 index 000000000..8fe03f252 --- /dev/null +++ b/src/proto/alpha/manager_repr.ml @@ -0,0 +1,53 @@ +(**************************************************************************) +(* *) +(* Copyright (c) 2014 - 2016. *) +(* Dynamic Ledger Solutions, Inc. *) +(* *) +(* All rights reserved. No warranty, explicit or implicit, provided. *) +(* *) +(**************************************************************************) + +(* Tezos Protocol Implementation - Low level Repr. of Managers' keys *) + +type manager_key = + | Hash of Ed25519.Public_key_hash.t + | Public_key of Ed25519.Public_key.t + +type t = manager_key + +let hash hash = Hash hash +let public_key hash = Public_key hash + +open Data_encoding + +let hash_encoding = + (obj1 + (req "hash" Ed25519.Public_key_hash.encoding) + ) + +let pubkey_encoding = + (obj1 + (req "public_key" Ed25519.Public_key.encoding) + ) + +let hash_case tag = + case ~tag hash_encoding + (function + | Hash hash -> Some hash + | _ -> None) + (fun hash -> Hash hash) + +let pubkey_case tag = + case ~tag pubkey_encoding + (function + | Public_key hash -> Some hash + | _ -> None) + (fun hash -> Public_key hash) + + +let encoding = + union [ + hash_case 0 ; + pubkey_case 1 ; + ] + diff --git a/src/proto/alpha/manager_repr.mli b/src/proto/alpha/manager_repr.mli new file mode 100644 index 000000000..3607c3a0b --- /dev/null +++ b/src/proto/alpha/manager_repr.mli @@ -0,0 +1,22 @@ +(**************************************************************************) +(* *) +(* Copyright (c) 2014 - 2016. *) +(* Dynamic Ledger Solutions, Inc. *) +(* *) +(* All rights reserved. No warranty, explicit or implicit, provided. *) +(* *) +(**************************************************************************) + +(* Tezos Protocol Implementation - Low level Repr. of Managers' keys *) + +(** The public key of the manager of a contract is reveled only after the + first operation. At Origination time, the manager provides only the hash + of its public key that is stored in the contract. When the public key + is actually reveeld, the public key instead of the hash of the key *) +type manager_key = + | Hash of Ed25519.Public_key_hash.t + | Public_key of Ed25519.Public_key.t + +type t = manager_key + +val encoding : t Data_encoding.encoding diff --git a/src/proto/alpha/storage.ml b/src/proto/alpha/storage.ml index 4adf6e3f7..f6f18c584 100644 --- a/src/proto/alpha/storage.ml +++ b/src/proto/alpha/storage.ml @@ -295,10 +295,10 @@ module Contract = struct module Manager = Make_indexed_data_storage(struct type key = Contract_repr.t - type value = Ed25519.Public_key_hash.t + type value = Manager_repr.t let name = "contract manager" let key = Key.Contract.manager - let encoding = Ed25519.Public_key_hash.encoding + let encoding = Manager_repr.encoding end) module Spendable = diff --git a/src/proto/alpha/storage.mli b/src/proto/alpha/storage.mli index ee4b5f243..554be84c0 100644 --- a/src/proto/alpha/storage.mli +++ b/src/proto/alpha/storage.mli @@ -136,7 +136,7 @@ module Contract : sig (** The manager of a contract *) module Manager : Indexed_data_storage with type key = Contract_repr.t - and type value = Ed25519.Public_key_hash.t + and type value = Manager_repr.t and type context := t (** The delegate of a contract, if any. *) diff --git a/src/proto/alpha/tezos_context.mli b/src/proto/alpha/tezos_context.mli index 3131c8aa0..71701c11f 100644 --- a/src/proto/alpha/tezos_context.mli +++ b/src/proto/alpha/tezos_context.mli @@ -424,6 +424,9 @@ module Contract : sig val get_manager: context -> contract -> public_key_hash tzresult Lwt.t + val update_manager_key: + context -> contract -> public_key option -> (context * public_key) tzresult Lwt.t + val get_delegate_opt: context -> contract -> public_key_hash option tzresult Lwt.t val is_delegatable: diff --git a/test/proto_alpha/proto_alpha_helpers.ml b/test/proto_alpha/proto_alpha_helpers.ml index fc4e95643..31c9aedca 100644 --- a/test/proto_alpha/proto_alpha_helpers.ml +++ b/test/proto_alpha/proto_alpha_helpers.ml @@ -224,6 +224,7 @@ module Account = struct let set_delegate ?(block = `Prevalidation) ?(fee = 5L) + ?src_pk ~contract ~manager_sk delegate_opt = @@ -234,6 +235,7 @@ module Account = struct ~source:contract ~manager_sk ~fee + ?src_pk delegate_opt let balance ?(block = `Prevalidation) (account : t) = @@ -370,7 +372,19 @@ module Assert = struct let inconsistent_pkh ~msg = Assert.contain_error ~msg ~f:begin ecoproto_error (function - | Public_key_storage.Inconsistent_hash _ -> true + | Contract_storage.Inconsistent_hash _ -> true + | _ -> false) + end + + let inconsistent_public_key ~msg = + Assert.contain_error ~msg ~f:begin ecoproto_error (function + | Contract_storage.Inconsistent_public_key _ -> true + | _ -> false) + end + + let missing_public_key ~msg = + Assert.contain_error ~msg ~f:begin ecoproto_error (function + | Contract_storage.Missing_public_key _ -> true | _ -> false) end diff --git a/test/proto_alpha/proto_alpha_helpers.mli b/test/proto_alpha/proto_alpha_helpers.mli index 52b4c6452..1a288e0a9 100644 --- a/test/proto_alpha/proto_alpha_helpers.mli +++ b/test/proto_alpha/proto_alpha_helpers.mli @@ -78,6 +78,7 @@ module Account : sig val set_delegate : ?block:Client_proto_rpcs.block -> ?fee:int64 -> + ?src_pk:public_key -> contract:Contract.t -> manager_sk:secret_key -> public_key_hash option -> @@ -176,6 +177,8 @@ module Assert : sig val balance_too_low : msg:string -> 'a tzresult -> unit val non_spendable : msg:string -> 'a tzresult -> unit val inconsistent_pkh : msg:string -> 'a tzresult -> unit + val inconsistent_public_key : msg:string -> 'a tzresult -> unit + val missing_public_key : msg:string -> 'a tzresult -> unit (** Origination assertions *) diff --git a/test/proto_alpha/test_origination.ml b/test/proto_alpha/test_origination.ml index eff35d7ae..501a6ada9 100644 --- a/test/proto_alpha/test_origination.ml +++ b/test/proto_alpha/test_origination.ml @@ -64,6 +64,7 @@ let run blkid ({ b1 ; b2 ; _ } : Helpers.Account.bootstrap_accounts) = (* Change delegate of a non-delegatable contract *) Helpers.Account.set_delegate + ~src_pk:b1.pk ~contract:nd_contract ~manager_sk:b1.sk (Some b2.pkh) >>= fun result -> @@ -71,6 +72,7 @@ let run blkid ({ b1 ; b2 ; _ } : Helpers.Account.bootstrap_accounts) = (* Change delegate of a delegatable contract *) Helpers.Account.set_delegate + ~src_pk:b1.pk ~contract:d_contract ~manager_sk:b1.sk (Some b2.pkh) >>= fun _result -> diff --git a/test/proto_alpha/test_transaction.ml b/test/proto_alpha/test_transaction.ml index 71da3c41f..57d2c41a0 100644 --- a/test/proto_alpha/test_transaction.ml +++ b/test/proto_alpha/test_transaction.ml @@ -100,7 +100,7 @@ let run blkid ({ b1 ; b2 ; b3 ; _ } : Helpers.Account.bootstrap_accounts) = ~account ~destination:b2.contract ~amount:10_00L () >>= fun result -> - Assert.inconsistent_pkh ~msg:__LOC__ result ; + Assert.inconsistent_public_key ~msg:__LOC__ result ; return blkh