diff --git a/smartcontract/service/native/ontid/attribute_test.go b/smartcontract/service/native/ontid/attribute_test.go new file mode 100644 index 0000000000..381963e6ff --- /dev/null +++ b/smartcontract/service/native/ontid/attribute_test.go @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2018 The ontology Authors + * This file is part of The ontology library. + * + * The ontology is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ontology is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with The ontology. If not, see . + */ +package ontid + +import ( + "bytes" + "fmt" + "testing" + + "github.com/ontio/ontology-crypto/keypair" + "github.com/ontio/ontology/account" + "github.com/ontio/ontology/common" + "github.com/ontio/ontology/smartcontract/service/native" + "github.com/ontio/ontology/smartcontract/service/native/utils" +) + +func TestAttribute(t *testing.T) { + testcase(t, CaseAttribute) +} + +func CaseAttribute(t *testing.T, n *native.NativeService) { + // 1. register id + a := account.NewAccount("") + id, err := account.GenerateID() + if err != nil { + t.Fatal("generate id error") + } + if err := regID(n, id, a); err != nil { + t.Fatal(err) + } + + attr := attribute{ + key: []byte("test key"), + valueType: []byte("test type"), + value: []byte("test value"), + } + + // 2. add attribute by invalid owner + a1 := account.NewAccount("") + sink := common.NewZeroCopySink(nil) + sink.WriteString(id) + utils.EncodeVarUint(sink, 1) + attr.Serialization(sink) + sink.WriteVarBytes(keypair.SerializePublicKey(a1.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a1.Address} + if _, err := addAttributes(n); err == nil { + t.Error("attribute added by invalid owner") + } + + // 3. add invalid attribute, should fail + sink.Reset() + sink.WriteString(id) + utils.EncodeVarUint(sink, 1) + sink.WriteVarBytes([]byte("invalid attribute")) + sink.WriteVarBytes(keypair.SerializePublicKey(a.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a.Address} + if _, err := addAttributes(n); err == nil { + t.Error("invalid attribute added") + } + + // 4. add attribute + sink.Reset() + sink.WriteString(id) + utils.EncodeVarUint(sink, 1) + attr.Serialization(sink) + sink.WriteVarBytes(keypair.SerializePublicKey(a.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a.Address} + if _, err := addAttributes(n); err != nil { + t.Fatal(err) + } + + // 5. check attribute + if err := checkAttribute(n, id, []attribute{attr}); err != nil { + t.Fatal(err) + } + + // 6. remove attribute by invalid owner + sink.Reset() + sink.WriteString(id) + sink.WriteVarBytes(attr.key) + sink.WriteVarBytes(keypair.SerializePublicKey(a1.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a1.Address} + if _, err := removeAttribute(n); err == nil { + t.Error("attribute removed by invalid owner") + } + + // 7. remove nonexistent attribute + sink.Reset() + sink.WriteString(id) + sink.WriteVarBytes([]byte("invalid attribute key")) + sink.WriteVarBytes(keypair.SerializePublicKey(a.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a.Address} + if _, err := removeAttribute(n); err == nil { + t.Error("attribute removed by invalid owner") + } + + // 8. remove attribute + sink.Reset() + sink.WriteString(id) + sink.WriteVarBytes(attr.key) + sink.WriteVarBytes(keypair.SerializePublicKey(a.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a.Address} + if _, err := removeAttribute(n); err != nil { + t.Fatal(err) + } + + // 9. check attribute + if err := checkAttribute(n, id, []attribute{}); err != nil { + t.Error("check attribute error,", err) + } + + // 10. attribute size limit + attr = attribute{ + key: make([]byte, MAX_KEY_SIZE+1), + valueType: []byte("test type"), + value: []byte("test value"), + } + sink.Reset() + sink.WriteString(id) + utils.EncodeVarUint(sink, 1) + attr.Serialization(sink) + sink.WriteVarBytes(keypair.SerializePublicKey(a.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a.Address} + if _, err := addAttributes(n); err == nil { + t.Error("attribute key size limit error") + } + attr = attribute{ + key: []byte("test key"), + valueType: []byte("test type"), + value: make([]byte, MAX_VALUE_SIZE+1), + } + sink.Reset() + sink.WriteString(id) + utils.EncodeVarUint(sink, 1) + attr.Serialization(sink) + sink.WriteVarBytes(keypair.SerializePublicKey(a.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a.Address} + if _, err := addAttributes(n); err == nil { + t.Error("attribute value size limit error") + } + attr = attribute{ + key: []byte("test key"), + valueType: make([]byte, MAX_TYPE_SIZE+1), + value: []byte("test value"), + } + sink.Reset() + sink.WriteString(id) + utils.EncodeVarUint(sink, 1) + attr.Serialization(sink) + sink.WriteVarBytes(keypair.SerializePublicKey(a.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a.Address} + if _, err := addAttributes(n); err == nil { + t.Error("attribute type size limit error") + } +} + +func checkAttribute(n *native.NativeService, id string, attributes []attribute) error { + sink := common.NewZeroCopySink(nil) + sink.WriteString(id) + n.Input = sink.Bytes() + res, err := GetAttributes(n) + if err != nil { + return err + } + + total := 0 + for _, a := range attributes { + sink.Reset() + a.Serialization(sink) + b := sink.Bytes() + if bytes.Index(res, b) == -1 { + return fmt.Errorf("attribute %s not found", string(a.key)) + } + total += len(b) + } + + if len(res) != total { + return fmt.Errorf("unmatched attribute number") + } + + return nil +} diff --git a/smartcontract/service/native/ontid/controller.go b/smartcontract/service/native/ontid/controller.go index d215b67cf1..340eb96e17 100644 --- a/smartcontract/service/native/ontid/controller.go +++ b/smartcontract/service/native/ontid/controller.go @@ -24,7 +24,6 @@ import ( "github.com/ontio/ontology-crypto/keypair" "github.com/ontio/ontology/account" "github.com/ontio/ontology/common" - "github.com/ontio/ontology/core/states" "github.com/ontio/ontology/smartcontract/service/native" "github.com/ontio/ontology/smartcontract/service/native/utils" ) @@ -75,7 +74,7 @@ func regIdWithController(srvc *native.NativeService) ([]byte, error) { key := append(encId, FIELD_CONTROLLER) utils.PutBytes(srvc, key, arg1) - srvc.CacheDB.Put(encId, states.GenRawStorageItem([]byte{flag_valid})) + utils.PutBytes(srvc, encId, []byte{flag_valid}) triggerRegisterEvent(srvc, arg0) return utils.BYTE_TRUE, nil } @@ -148,16 +147,8 @@ func removeController(srvc *native.NativeService) ([]byte, error) { if err != nil { return utils.BYTE_FALSE, err } - pk, err := getPk(srvc, encId, uint32(arg1)) - if err != nil { - return utils.BYTE_FALSE, err - } - if pk.revoked { - return utils.BYTE_FALSE, fmt.Errorf("authentication failed, public key is removed") - } - err = checkWitness(srvc, pk.key) - if err != nil { - return utils.BYTE_FALSE, fmt.Errorf("checkWitness failed") + if err := checkWitnessByIndex(srvc, encId, uint32(arg1)); err != nil { + return utils.BYTE_FALSE, fmt.Errorf("checkWitness failed, %s", err) } key := append(encId, FIELD_CONTROLLER) srvc.CacheDB.Delete(key) @@ -338,18 +329,7 @@ func verifySingleController(srvc *native.NativeService, id []byte, args *common. if err != nil { return err } - pk, err := getPk(srvc, encId, uint32(index)) - if err != nil { - return err - } - if pk.revoked { - return fmt.Errorf("revoked key") - } - err = checkWitness(srvc, pk.key) - if err != nil { - return err - } - return nil + return checkWitnessByIndex(srvc, encId, uint32(index)) } func verifyGroupController(srvc *native.NativeService, group *Group, args *common.ZeroCopySource) error { diff --git a/smartcontract/service/native/ontid/controller_test.go b/smartcontract/service/native/ontid/controller_test.go new file mode 100644 index 0000000000..52a8e6686e --- /dev/null +++ b/smartcontract/service/native/ontid/controller_test.go @@ -0,0 +1,431 @@ +/* + * Copyright (C) 2018 The ontology Authors + * This file is part of The ontology library. + * + * The ontology is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ontology is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with The ontology. If not, see . + */ +package ontid + +import ( + "bytes" + "testing" + + "github.com/ontio/ontology-crypto/keypair" + "github.com/ontio/ontology/account" + "github.com/ontio/ontology/common" + "github.com/ontio/ontology/smartcontract/service/native" + "github.com/ontio/ontology/smartcontract/service/native/utils" +) + +func TestCaseController(t *testing.T) { + testcase(t, CaseController) +} + +func TestGroupController(t *testing.T) { + testcase(t, CaseGroupController) +} + +// Test case: register an ID controlled by another ID +func CaseController(t *testing.T, n *native.NativeService) { + a0 := account.NewAccount("") + id0, _ := account.GenerateID() + id1, _ := account.GenerateID() + + // 1. unregistered controller, should fail + if err := regControlledID(n, id1, id0, 1, a0.Address); err == nil { + t.Error("registered controlled id with unregistered controller") + } + + // 2. register the controller + if err := regID(n, id0, a0); err != nil { + t.Fatal(err) + } + + // 3. register without valid signature, should fail + if err := regControlledID(n, id1, id0, 1, common.ADDRESS_EMPTY); err == nil { + t.Error("registered without valid signature") + } + + // 4. register with invalid key index, should fail + if err := regControlledID(n, id1, id0, 2, a0.Address); err == nil { + t.Error("registered with invalid key index") + } + + // 5. register with invalid id, should fail + if err := regControlledID(n, "did:ont::123", id0, 1, a0.Address); err == nil { + t.Error("invalid id registered") + } + + // 6. register the controlled ID + if err := regControlledID(n, id1, id0, 1, a0.Address); err != nil { + t.Fatal(err) + } + + // 7. register again, should fail + if err := regControlledID(n, id1, id0, 1, a0.Address); err == nil { + t.Fatal("register twice") + } + + // 8. verify controller + if ok, err := verifyCtrl(n, id1, 1, a0.Address); !ok || err != nil { + t.Fatal("verify controller error", err) + } + + // 9. verify invalid controller, should fail + if ok, err := verifyCtrl(n, id1, 2, a0.Address); ok && err == nil { + t.Error("invalid controller key index passed verification") + } + + // 10. verify controller without valid signature, should fail + if ok, err := verifyCtrl(n, id1, 1, common.ADDRESS_EMPTY); ok && err == nil { + t.Error("controller passed verification without valid signature") + } + + // 11. add attribute by invalid controller, should fail + attr := attribute{ + []byte("test key"), + []byte("test value"), + []byte("test type"), + } + if err := ctrlAddAttr(n, id1, attr, 1, common.Address{}); err == nil { + t.Error("attribute added by invalid controller") + } + + // 12. add attribute + if err := ctrlAddAttr(n, id1, attr, 1, a0.Address); err != nil { + t.Fatal(err) + } + + // 13. check attribute + if err := checkAttribute(n, id1, []attribute{attr}); err != nil { + t.Error("check attribute error", err) + } + + // 14. remove attribute by invalid controller, should fail + if err := ctrlRmAttr(n, id1, attr.key, 1, common.Address{}); err == nil { + t.Error("attribute removed by invalid controller") + } + + // 15. remove nonexistent attribute, should fail + if err := ctrlRmAttr(n, id1, []byte("unknown key"), 1, a0.Address); err == nil { + t.Error("removed nonexistent attribute") + } + + // 16. remove attribute by controller + if err := ctrlRmAttr(n, id1, attr.key, 1, a0.Address); err != nil { + t.Fatal(err) + } + + // 17. add invalid key, should fail + if err := ctrlAddKey(n, id1, []byte("test invalid key"), 1, a0.Address); err == nil { + t.Error("invalid key added by controller") + } + + // 18. add key by invalid controller, should fail + a1 := account.NewAccount("") + pk := keypair.SerializePublicKey(a1.PubKey()) + if err := ctrlAddKey(n, id1, pk, 1, common.Address{}); err == nil { + t.Error("key added by invalid controller") + } + + // 19. add key + if err := ctrlAddKey(n, id1, pk, 1, a0.Address); err != nil { + t.Fatal(err) + } + + // 20. remove key by invalid controller, should fail + if err := ctrlRmKey(n, id1, 1, 1, common.ADDRESS_EMPTY); err == nil { + t.Error("key removed by invalid controller") + } + + // 21. remove invalid key, should fail + if err := ctrlRmKey(n, id1, 2, 1, a0.Address); err == nil { + t.Error("invlid key removed") + } + + // 22. remove key + if err := ctrlRmKey(n, id1, 1, 1, a0.Address); err != nil { + t.Fatal(err) + } + + // 23. add the removed key again, should fail + if err := ctrlAddKey(n, id1, pk, 1, a0.Address); err == nil { + t.Error("removed key added again") + } + + // 24. add a new key + a2 := account.NewAccount("") + pk = keypair.SerializePublicKey(a2.PubKey()) + if err := ctrlAddKey(n, id1, pk, 1, a0.Address); err != nil { + t.Fatal(err) + } + + // 25, remove controller by invalid key, should fail + if err := rmCtrl(n, id1, 1, a1.Address); err == nil { + t.Error("controller removed by invalid key") + } + + // 26. remove controller without valid signature, should fail + if err := rmCtrl(n, id1, 2, common.Address{}); err == nil { + t.Error("controller removed without valid signature") + } + + // 27. remove contoller + if err := rmCtrl(n, id1, 2, a2.Address); err != nil { + t.Fatal(err) + } + + // 28. use removed controller, should all fail + if ok, err := verifyCtrl(n, id1, 1, a0.Address); ok && err == nil { + t.Error("removed controller passed verification") + } + if err := ctrlAddAttr(n, id1, attr, 1, a0.Address); err == nil { + t.Error("attribute added by removed controller") + } + a3 := account.NewAccount("") + pk = keypair.SerializePublicKey(a3.PubKey()) + if err := ctrlAddKey(n, id1, pk, 1, a0.Address); err == nil { + t.Error("key added by removed controller") + } +} + +func CaseGroupController(t *testing.T, n *native.NativeService) { + id, _ := account.GenerateID() + id0, _ := account.GenerateID() + id1, _ := account.GenerateID() + id2, _ := account.GenerateID() + id3, _ := account.GenerateID() + a0 := account.NewAccount("") + a1 := account.NewAccount("") + a2 := account.NewAccount("") + a3 := account.NewAccount("") + + // controller group + g := &Group{ + Threshold: 2, + Members: []interface{}{ + []byte(id0), + &Group{ + Threshold: 1, + Members: []interface{}{ + []byte(id1), + []byte(id2), + }, + }, + }, + } + // signers + signers := []Signer{ + Signer{[]byte(id0), 1}, + Signer{[]byte(id1), 1}, + Signer{[]byte(id2), 1}, + } + // signed addresses + addr := []common.Address{a0.Address, a1.Address, a2.Address} + + // 1. register id by unregistered controllers, should fail + if err := regGroupControlledID(n, id, g, signers, addr); err == nil { + t.Error("controlled id registered with unregistered controllers") + } + + // 2. register controllers + if err := regID(n, id0, a0); err != nil { + t.Fatal("register id0 error") + } + if err := regID(n, id1, a1); err != nil { + t.Fatal("register id1 error") + } + if err := regID(n, id2, a2); err != nil { + t.Fatal("register id2 error") + } + if err := regID(n, id3, a3); err != nil { + t.Fatal("register id3 error") + } + + // 3. register without valid signature, should fail + if err := regGroupControlledID(n, id, g, signers, addr[1:]); err == nil { + t.Error("registered without valid signatures") + } + + // 4. register without enough signers, should fail + if err := regGroupControlledID(n, id, g, signers[1:], addr[1:]); err == nil { + t.Error("registered without enough signers") + } + + // 5. register with invalid signers, should fail + signers[0].id = []byte(id3) + addr[0] = a3.Address + if err := regGroupControlledID(n, id, g, signers, addr); err == nil { + t.Error("registered invalid controller") + } + + // 5. register controlled id + signers[0].id = []byte(id0) + addr[0] = a0.Address + if err := regGroupControlledID(n, id, g, signers, addr); err != nil { + t.Fatal(err) + } + + // 6. verify controller + if ok, err := verifyGroupCtrl(n, id, signers, addr); !ok || err != nil { + t.Error("verify group controller failed", err) + } + + // 7. verify invalid controller, should fail + if ok, err := verifyGroupCtrl(n, id, signers[1:], addr[1:]); ok && err == nil { + t.Error("invalid group controller passed verification") + } + + // 8. revoke id by invalid controller, should fail + if err := revokeByCtrl(n, id, signers[1:], addr[1:]); err == nil { + t.Error("id revoked by invalid controller") + } + + // 9. revoke id by controller + if err := revokeByCtrl(n, id, signers, addr); err != nil { + t.Fatal(err) + } + + // 10. check id state + enc, _ := encodeID([]byte(id)) + if checkIDState(n, enc) != flag_revoke { + t.Fatal("id state is not revoked") + } + + // 11. verify controller, should fail + if ok, err := verifyGroupCtrl(n, id, signers, addr); ok && err == nil { + t.Error("revoked id passed verification") + } + + // 12. register again, should fail + if err := regGroupControlledID(n, id, g, signers, addr); err == nil { + t.Error("revoked id should not be registered again") + } +} + +// Register id0 which is controlled by id1 +func regControlledID(n *native.NativeService, id0, id1 string, index uint64, addr common.Address) error { + // make arguments + sink := common.NewZeroCopySink(nil) + sink.WriteVarBytes([]byte(id0)) + sink.WriteVarBytes([]byte(id1)) + utils.EncodeVarUint(sink, index) + n.Input = sink.Bytes() + // set signing address + n.Tx.SignedAddr = []common.Address{addr} + // call + _, err := regIdWithController(n) + return err +} + +func verifyCtrl(n *native.NativeService, id string, index uint64, addr common.Address) (bool, error) { + sink := common.NewZeroCopySink(nil) + sink.WriteString(id) + utils.EncodeVarUint(sink, index) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{addr} + res, err := verifyController(n) + return bytes.Equal(res, utils.BYTE_TRUE), err +} + +func ctrlAddAttr(n *native.NativeService, id string, attr attribute, index uint64, addr common.Address) error { + sink := common.NewZeroCopySink(nil) + sink.WriteString(id) + // attribute + utils.EncodeVarUint(sink, 1) + attr.Serialization(sink) + // signer + utils.EncodeVarUint(sink, index) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{addr} + _, err := addAttributesByController(n) + return err +} + +func ctrlRmAttr(n *native.NativeService, id string, key []byte, index uint64, addr common.Address) error { + sink := common.NewZeroCopySink(nil) + sink.WriteString(id) + sink.WriteVarBytes(key) + utils.EncodeVarUint(sink, index) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{addr} + _, err := removeAttributeByController(n) + return err +} + +func ctrlAddKey(n *native.NativeService, id string, key []byte, index uint64, addr common.Address) error { + sink := common.NewZeroCopySink(nil) + sink.WriteString(id) + // key + sink.WriteVarBytes(key) + // signer + utils.EncodeVarUint(sink, index) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{addr} + _, err := addKeyByController(n) + return err +} + +func ctrlRmKey(n *native.NativeService, id string, keyIndex, signIndex uint64, addr common.Address) error { + sink := common.NewZeroCopySink(nil) + sink.WriteString(id) + utils.EncodeVarUint(sink, keyIndex) + utils.EncodeVarUint(sink, signIndex) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{addr} + _, err := removeKeyByController(n) + return err +} + +func rmCtrl(n *native.NativeService, id string, index uint64, addr common.Address) error { + sink := common.NewZeroCopySink(nil) + sink.WriteString(id) + // signing key index + utils.EncodeVarUint(sink, index) + n.Input = sink.Bytes() + // set signing address + n.Tx.SignedAddr = []common.Address{addr} + _, err := removeController(n) + return err +} + +func regGroupControlledID(n *native.NativeService, id string, g *Group, s []Signer, addr []common.Address) error { + sink := common.NewZeroCopySink(nil) + sink.WriteString(id) + sink.WriteVarBytes(g.Serialize()) + sink.WriteVarBytes(SerializeSigners(s)) + n.Input = sink.Bytes() + n.Tx.SignedAddr = addr + _, err := regIdWithController(n) + return err +} + +func verifyGroupCtrl(n *native.NativeService, id string, s []Signer, addr []common.Address) (bool, error) { + sink := common.NewZeroCopySink(nil) + sink.WriteString(id) + sink.WriteVarBytes(SerializeSigners(s)) + n.Input = sink.Bytes() + n.Tx.SignedAddr = addr + res, err := verifyController(n) + return bytes.Equal(res, utils.BYTE_TRUE), err +} +func revokeByCtrl(n *native.NativeService, id string, s []Signer, addr []common.Address) error { + sink := common.NewZeroCopySink(nil) + sink.WriteString(id) + sink.WriteVarBytes(SerializeSigners(s)) + n.Input = sink.Bytes() + n.Tx.SignedAddr = addr + _, err := revokeIDByController(n) + return err +} diff --git a/smartcontract/service/native/ontid/group.go b/smartcontract/service/native/ontid/group.go index 84f5798ee9..9b68d86e0c 100644 --- a/smartcontract/service/native/ontid/group.go +++ b/smartcontract/service/native/ontid/group.go @@ -40,6 +40,23 @@ func (g *Group) ToJson() []byte { return j } +func (g *Group) Serialize() []byte { + sink := common.NewZeroCopySink(nil) + utils.EncodeVarUint(sink, uint64(len(g.Members))) + for _, m := range g.Members { + switch t := m.(type) { + case []byte: + sink.WriteVarBytes(t) + case *Group: + sink.WriteVarBytes(t.Serialize()) + default: + panic("invlid member type") + } + } + utils.EncodeVarUint(sink, uint64(g.Threshold)) + return sink.Bytes() +} + func rDeserialize(data []byte, depth uint) (*Group, error) { if depth == MAX_DEPTH { return nil, fmt.Errorf("recursion is too deep") @@ -122,6 +139,16 @@ type Signer struct { index uint32 } +func SerializeSigners(s []Signer) []byte { + sink := common.NewZeroCopySink(nil) + utils.EncodeVarUint(sink, uint64(len(s))) + for _, v := range s { + sink.WriteVarBytes(v.id) + utils.EncodeVarUint(sink, uint64(v.index)) + } + return sink.Bytes() +} + func deserializeSigners(data []byte) ([]Signer, error) { buf := common.NewZeroCopySource(data) num, err := utils.DecodeVarUint(buf) @@ -185,14 +212,7 @@ func verifyGroupSignature(srvc *native.NativeService, g *Group, signers []Signer if err != nil { return false } - pk, err := getPk(srvc, key, signer.index) - if err != nil { - return false - } - if pk.revoked { - return false - } - if checkWitness(srvc, pk.key) != nil { + if checkWitnessByIndex(srvc, key, signer.index) != nil { return false } } diff --git a/smartcontract/service/native/ontid/method.go b/smartcontract/service/native/ontid/method.go index fb1ee14244..a64f19f7f0 100644 --- a/smartcontract/service/native/ontid/method.go +++ b/smartcontract/service/native/ontid/method.go @@ -27,7 +27,6 @@ import ( "github.com/ontio/ontology/account" "github.com/ontio/ontology/common" "github.com/ontio/ontology/common/log" - "github.com/ontio/ontology/core/states" "github.com/ontio/ontology/core/types" "github.com/ontio/ontology/smartcontract/service/native" "github.com/ontio/ontology/smartcontract/service/native/utils" @@ -73,7 +72,6 @@ func regIdWithPublicKey(srvc *native.NativeService) ([]byte, error) { public, err := keypair.DeserializePublicKey(arg1) if err != nil { - log.Error(err) return utils.BYTE_FALSE, errors.New("register ONT ID error: invalid public key") } addr := types.AddressFromPubKey(public) @@ -87,7 +85,7 @@ func regIdWithPublicKey(srvc *native.NativeService) ([]byte, error) { return utils.BYTE_FALSE, errors.New("register ONT ID error: store public key error, " + err.Error()) } // set flags - srvc.CacheDB.Put(key, states.GenRawStorageItem([]byte{flag_valid})) + utils.PutBytes(srvc, key, []byte{flag_valid}) triggerRegisterEvent(srvc, arg0) @@ -159,7 +157,7 @@ func regIdWithAttributes(srvc *native.NativeService) ([]byte, error) { return utils.BYTE_FALSE, errors.New("register ID with attributes error: insert attribute error: " + err.Error()) } - srvc.CacheDB.Put(key, states.GenRawStorageItem([]byte{flag_valid})) + utils.PutBytes(srvc, key, []byte{flag_valid}) triggerRegisterEvent(srvc, arg0) return utils.BYTE_TRUE, nil } @@ -214,11 +212,6 @@ func addKey(srvc *native.NativeService) ([]byte, error) { } } - item, _, err := findPk(srvc, key, arg1) - if item != 0 { - return utils.BYTE_FALSE, errors.New("add key failed: already exists") - } - keyID, err := insertPk(srvc, key, arg1) if err != nil { return utils.BYTE_FALSE, errors.New("add key failed: insert public key error, " + err.Error()) @@ -393,17 +386,7 @@ func verifySignature(srvc *native.NativeService) ([]byte, error) { if err != nil { return utils.BYTE_FALSE, errors.New("verify signature error: " + err.Error()) } - owner, err := getPk(srvc, key, uint32(arg1)) - if err != nil { - return utils.BYTE_FALSE, errors.New("verify signature error: get key failed, " + err.Error()) - } else if owner == nil { - return utils.BYTE_FALSE, errors.New("verify signature error: public key not found") - } else if owner.revoked { - return utils.BYTE_FALSE, errors.New("verify signature error: revoked key") - } - - err = checkWitness(srvc, owner.key) - if err != nil { + if err := checkWitnessByIndex(srvc, key, uint32(arg1)); err != nil { return utils.BYTE_FALSE, errors.New("verify signature failed: " + err.Error()) } @@ -432,15 +415,8 @@ func revokeID(srvc *native.NativeService) ([]byte, error) { return utils.BYTE_FALSE, fmt.Errorf("%s is not registered or already revoked", string(arg0)) } - pk, err := getPk(srvc, encID, uint32(arg1)) - if err != nil { - return utils.BYTE_FALSE, fmt.Errorf("get public key error: %s", err) - } else if pk.revoked { - return utils.BYTE_FALSE, fmt.Errorf("revoked key") - } - - if checkWitness(srvc, pk.key) != nil { - return utils.BYTE_FALSE, fmt.Errorf("authorization failed") + if err := checkWitnessByIndex(srvc, encID, uint32(arg1)); err != nil { + return utils.BYTE_FALSE, fmt.Errorf("authorization failed, %s", err) } err = deleteID(srvc, encID) diff --git a/smartcontract/service/native/ontid/ontid_test.go b/smartcontract/service/native/ontid/ontid_test.go new file mode 100644 index 0000000000..4d2d65ac99 --- /dev/null +++ b/smartcontract/service/native/ontid/ontid_test.go @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2018 The ontology Authors + * This file is part of The ontology library. + * + * The ontology is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ontology is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with The ontology. If not, see . + */ +package ontid + +import ( + "bytes" + "testing" + + "github.com/ontio/ontology-crypto/keypair" + "github.com/ontio/ontology/account" + "github.com/ontio/ontology/common" + "github.com/ontio/ontology/smartcontract/service/native" + "github.com/ontio/ontology/smartcontract/service/native/testsuite" + "github.com/ontio/ontology/smartcontract/service/native/utils" +) + +func testcase(t *testing.T, f func(t *testing.T, n *native.NativeService)) { + testsuite.InvokeNativeContract(t, utils.OntIDContractAddress, + func(n *native.NativeService) ([]byte, error) { + f(t, n) + return nil, nil + }, + ) +} + +func TestReg(t *testing.T) { + testcase(t, CaseRegID) +} + +func TestOwner(t *testing.T) { + testcase(t, CaseOwner) +} + +func TestOwnerSize(t *testing.T) { + testcase(t, CaseOwnerSize) +} + +// Register id with account acc +func regID(n *native.NativeService, id string, a *account.Account) error { + // make arguments + sink := common.NewZeroCopySink(nil) + sink.WriteVarBytes([]byte(id)) + pk := keypair.SerializePublicKey(a.PubKey()) + sink.WriteVarBytes(pk) + n.Input = sink.Bytes() + // set signing address + n.Tx.SignedAddr = []common.Address{a.Address} + // call + _, err := regIdWithPublicKey(n) + return err +} + +func CaseRegID(t *testing.T, n *native.NativeService) { + id, err := account.GenerateID() + if err != nil { + t.Fatal(err) + } + a := account.NewAccount("") + + // 1. register invalid id, should fail + if err := regID(n, "did:ont:abcd1234", a); err == nil { + t.Error("invalid id registered") + } + + // 2. register without valid signature, should fail + sink := common.NewZeroCopySink(nil) + sink.WriteString(id) + sink.WriteVarBytes(keypair.SerializePublicKey(a.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{} + if _, err := regIdWithPublicKey(n); err == nil { + t.Error("id registered without signature") + } + + // 3. register with invalid key, should fail + sink.Reset() + sink.WriteString(id) + sink.WriteVarBytes([]byte("invalid public key")) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a.Address} + if _, err := regIdWithPublicKey(n); err == nil { + t.Error("id registered with invalid key") + } + + // 4. register id + if err := regID(n, id, a); err != nil { + t.Fatal(err) + } + + // 5. get DDO + sink.Reset() + sink.WriteString(id) + n.Input = sink.Bytes() + _, err = GetDDO(n) + if err != nil { + t.Error(err) + } + + // 6. register again, should fail + if err := regID(n, id, a); err == nil { + t.Error("id registered twice") + } + + // 7. revoke with invalid key, should fail + sink.Reset() + sink.WriteString(id) + utils.EncodeVarUint(sink, 2) + n.Input = sink.Bytes() + if _, err := revokeID(n); err == nil { + t.Error("revoked by invalid key") + } + + // 8. revoke without valid signature, should fail + sink.Reset() + sink.WriteString(id) + utils.EncodeVarUint(sink, 1) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{common.ADDRESS_EMPTY} + if _, err := revokeID(n); err == nil { + t.Error("revoked without valid signature") + } + + // 9. revoke id + sink.Reset() + sink.WriteString(id) + utils.EncodeVarUint(sink, 1) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a.Address} + if _, err := revokeID(n); err != nil { + t.Fatal(err) + } + + // 10. register again, should fail + if err := regID(n, id, a); err == nil { + t.Error("revoked id should not be registered again") + } + + // 11. get DDO of the revoked id + sink.Reset() + sink.WriteString(id) + n.Input = sink.Bytes() + _, err = GetDDO(n) + if err == nil { + t.Error("get DDO of the revoked id should fail") + } + +} + +func CaseOwner(t *testing.T, n *native.NativeService) { + // 1. register ID + id, err := account.GenerateID() + if err != nil { + t.Fatal("generate ID error") + } + a0 := account.NewAccount("") + if err := regID(n, id, a0); err != nil { + t.Fatal("register ID error", err) + } + + // 2. add key without valid signature, should fail + a1 := account.NewAccount("") + sink := common.NewZeroCopySink(nil) + sink.WriteString(id) + sink.WriteVarBytes(keypair.SerializePublicKey(a1.PubKey())) + sink.WriteVarBytes(keypair.SerializePublicKey(a0.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{common.ADDRESS_EMPTY} + if _, err = addKey(n); err == nil { + t.Error("key added without valid signature") + } + + // 3. add key by invalid owner, should fail + a2 := account.NewAccount("") + sink.Reset() + sink.WriteString(id) + sink.WriteVarBytes(keypair.SerializePublicKey(a1.PubKey())) + sink.WriteVarBytes(keypair.SerializePublicKey(a2.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a2.Address} + if _, err = addKey(n); err == nil { + t.Error("key added by invalid owner") + } + + // 4. add invalid key, should fail + sink.Reset() + sink.WriteString(id) + sink.WriteVarBytes([]byte("test invalid key")) + sink.WriteVarBytes(keypair.SerializePublicKey(a0.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a0.Address} + if _, err = addKey(n); err == nil { + t.Error("invalid key added") + } + + // 5. add key + sink.Reset() + sink.WriteString(id) + sink.WriteVarBytes(keypair.SerializePublicKey(a1.PubKey())) + sink.WriteVarBytes(keypair.SerializePublicKey(a0.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a0.Address} + if _, err = addKey(n); err != nil { + t.Fatal(err) + } + + // 6. verify new key + sink.Reset() + sink.WriteString(id) + utils.EncodeVarUint(sink, 2) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a1.Address} + res, err := verifySignature(n) + if err != nil || !bytes.Equal(res, utils.BYTE_TRUE) { + t.Fatal("verify the added key failed") + } + + // 7. add key again, should fail + sink.Reset() + sink.WriteString(id) + sink.WriteVarBytes(keypair.SerializePublicKey(a1.PubKey())) + sink.WriteVarBytes(keypair.SerializePublicKey(a0.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a0.Address} + if _, err = addKey(n); err == nil { + t.Fatal("should not add the same key twice") + } + + // 8. remove key without valid signature, should fail + sink.Reset() + sink.WriteString(id) + sink.WriteVarBytes(keypair.SerializePublicKey(a0.PubKey())) + sink.WriteVarBytes(keypair.SerializePublicKey(a1.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a2.Address} + if _, err = removeKey(n); err == nil { + t.Error("key removed without valid signature") + } + + // 9. remove key by invalid owner, should fail + sink.Reset() + sink.WriteString(id) + sink.WriteVarBytes(keypair.SerializePublicKey(a0.PubKey())) + sink.WriteVarBytes(keypair.SerializePublicKey(a2.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a2.Address} + if _, err = removeKey(n); err == nil { + t.Error("key removed by invalid owner") + } + + // 10. remove invalid key, should fail + sink.Reset() + sink.WriteString(id) + sink.WriteVarBytes(keypair.SerializePublicKey(a2.PubKey())) + sink.WriteVarBytes(keypair.SerializePublicKey(a1.PubKey())) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a1.Address} + if _, err = removeKey(n); err == nil { + t.Error("invalid key removed") + } + + // 11. remove key + sink.Reset() + sink.WriteString(id) + sink.WriteVarBytes(keypair.SerializePublicKey(a0.PubKey())) + sink.WriteVarBytes(keypair.SerializePublicKey(a1.PubKey())) + n.Input = sink.Bytes() + if _, err = removeKey(n); err != nil { + t.Fatal(err) + } + + // 12. check removed key + sink.Reset() + sink.WriteString(id) + utils.EncodeVarUint(sink, 1) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a0.Address} + res, err = verifySignature(n) + if err == nil && bytes.Equal(res, utils.BYTE_TRUE) { + t.Fatal("removed key passed verification") + } + + // 13. add removed key again, should fail + sink.Reset() + sink.WriteString(id) + sink.WriteVarBytes(keypair.SerializePublicKey(a0.PubKey())) + sink.WriteVarBytes(keypair.SerializePublicKey(a1.PubKey())) + n.Input = sink.Bytes() + res, err = verifySignature(n) + if err == nil && bytes.Equal(res, utils.BYTE_TRUE) { + t.Error("the removed key should not be added again") + } + + // 14. query removed key + sink.Reset() + sink.WriteString(id) + sink.WriteInt32(1) + n.Input = sink.Bytes() + _, err = GetPublicKeyByID(n) + if err == nil { + t.Error("query removed key should fail") + } +} + +func CaseOwnerSize(t *testing.T, n *native.NativeService) { + id, _ := account.GenerateID() + a := account.NewAccount("") + err := regID(n, id, a) + if err != nil { + t.Fatal(err) + } + + enc, err := encodeID([]byte(id)) + if err != nil { + t.Fatal(err) + } + + buf := make([]byte, OWNER_TOTAL_SIZE) + _, err = insertPk(n, enc, buf) + if err == nil { + t.Fatal("total size of the owner's key should be limited") + } +} diff --git a/smartcontract/service/native/ontid/owner.go b/smartcontract/service/native/ontid/owner.go index 0f1cc66cc6..b857a882ad 100644 --- a/smartcontract/service/native/ontid/owner.go +++ b/smartcontract/service/native/ontid/owner.go @@ -96,6 +96,11 @@ func insertPk(srvc *native.NativeService, encID, pk []byte) (uint32, error) { if err != nil { owners = make([]*owner, 0) } + for _, k := range owners { + if bytes.Equal(k.key, pk) { + return 0, errors.New("the key is already added") + } + } size := len(owners) owners = append(owners, &owner{pk, false}) err = putAllPk(srvc, key, owners) diff --git a/smartcontract/service/native/ontid/query.go b/smartcontract/service/native/ontid/query.go index a1ee029750..a589c51774 100644 --- a/smartcontract/service/native/ontid/query.go +++ b/smartcontract/service/native/ontid/query.go @@ -93,8 +93,10 @@ func GetDDO(srvc *native.NativeService) ([]byte, error) { } sink.WriteVarBytes(var1) - // old recovery, always 0 - sink.WriteVarBytes([]byte{}) + // old recovery + // ignore error + oldRec, _ := getOldRecovery(srvc, key) + sink.WriteVarBytes(oldRec) // controller con, err := getController(srvc, key) diff --git a/smartcontract/service/native/ontid/recovery.go b/smartcontract/service/native/ontid/recovery.go index 3153c25fa2..8aa15a46f3 100644 --- a/smartcontract/service/native/ontid/recovery.go +++ b/smartcontract/service/native/ontid/recovery.go @@ -28,48 +28,45 @@ import ( "github.com/ontio/ontology/smartcontract/service/native/utils" ) +const ( + _VERSION_0 byte = 0x00 + _VERSION_1 byte = 0x01 +) + func setRecovery(srvc *native.NativeService) ([]byte, error) { source := common.NewZeroCopySource(srvc.Input) // arg0: ID arg0, err := utils.DecodeVarBytes(source) if err != nil { - return utils.BYTE_FALSE, errors.New("set recovery failed: argument 0 error") + return utils.BYTE_FALSE, errors.New("setRecovery: argument 0 error") } // arg1: recovery struct arg1, err := utils.DecodeVarBytes(source) if err != nil { - return utils.BYTE_FALSE, errors.New("set recovery failed: argument 1 error") + return utils.BYTE_FALSE, errors.New("setRecovery: argument 1 error") } // arg2: operator's public key index arg2, err := utils.DecodeVarUint(source) if err != nil { - return utils.BYTE_FALSE, errors.New("set recovery failed: argument 2 error") + return utils.BYTE_FALSE, errors.New("setRecovery: argument 2 error") } encId, err := encodeID(arg0) if err != nil { - return utils.BYTE_FALSE, errors.New("set recovery failed: " + err.Error()) + return utils.BYTE_FALSE, errors.New("setRecovery: " + err.Error()) } - pk, err := getPk(srvc, encId, uint32(arg2)) - if err != nil { - return utils.BYTE_FALSE, err - } - if pk.revoked { - return utils.BYTE_FALSE, errors.New("authentication failed, public key is revoked") - } - err = checkWitness(srvc, pk.key) - if err != nil { - return utils.BYTE_FALSE, errors.New("checkWitness failed") + if err := checkWitnessByIndex(srvc, encId, uint32(arg2)); err != nil { + return utils.BYTE_FALSE, errors.New("setRecovery: authentication failed: " + err.Error()) } re, err := getRecovery(srvc, encId) if err == nil && re != nil { - return utils.BYTE_FALSE, errors.New("recovery is already set") + return utils.BYTE_FALSE, errors.New("setRecovery: recovery is already set") } re, err = putRecovery(srvc, encId, arg1) if err != nil { - return utils.BYTE_FALSE, errors.New("set recovery failed: " + err.Error()) + return utils.BYTE_FALSE, errors.New("setRecovery: " + err.Error()) } newEvent(srvc, []interface{}{"recovery", "set", string(arg0), re.ToJson()}) @@ -81,38 +78,38 @@ func updateRecovery(srvc *native.NativeService) ([]byte, error) { // arg0: ID arg0, err := utils.DecodeVarBytes(source) if err != nil { - return utils.BYTE_FALSE, errors.New("argument 0 error") + return utils.BYTE_FALSE, errors.New("updateRecovery: argument 0 error") } // arg1: new recovery arg1, err := utils.DecodeVarBytes(source) if err != nil { - return utils.BYTE_FALSE, errors.New("argument 1 error") + return utils.BYTE_FALSE, errors.New("updateRecovery: argument 1 error") } // arg2: signers arg2, err := utils.DecodeVarBytes(source) if err != nil { - return utils.BYTE_FALSE, errors.New("argument 2 error") + return utils.BYTE_FALSE, errors.New("updateRecovery: argument 2 error") } key, err := encodeID(arg0) if err != nil { - return utils.BYTE_FALSE, errors.New("update recovery failed: " + err.Error()) + return utils.BYTE_FALSE, errors.New("update recovery: " + err.Error()) } re, err := getRecovery(srvc, key) if err != nil { - return utils.BYTE_FALSE, errors.New("update recovery failed: recovery not set") + return utils.BYTE_FALSE, errors.New("update recovery: get old recovery error, " + err.Error()) } signers, err := deserializeSigners(arg2) if err != nil { - return utils.BYTE_FALSE, errors.New("signers error: " + err.Error()) + return utils.BYTE_FALSE, errors.New("update recovery: signers error: " + err.Error()) } if !verifyGroupSignature(srvc, re, signers) { - return utils.BYTE_FALSE, errors.New("verification failed") + return utils.BYTE_FALSE, errors.New("update recovery: verification failed") } re, err = putRecovery(srvc, key, arg1) if err != nil { - return utils.BYTE_FALSE, errors.New("update recovery failed: " + err.Error()) + return utils.BYTE_FALSE, errors.New("update recovery: " + err.Error()) } newEvent(srvc, []interface{}{"Recovery", "update", string(arg0), re.ToJson()}) @@ -221,7 +218,10 @@ func putRecovery(srvc *native.NativeService, encID, data []byte) (*Group, error) return nil, fmt.Errorf("invalid recovery member, %s", err) } key := append(encID, FIELD_RECOVERY) - utils.PutBytes(srvc, key, data) + item := states.StorageItem{} + item.Value = data + item.StateVersion = _VERSION_1 // storage version + srvc.CacheDB.Put(key, item.ToArray()) return rec, nil } @@ -233,6 +233,9 @@ func getRecovery(srvc *native.NativeService, encID []byte) (*Group, error) { } else if item == nil { return nil, errors.New("empty storage item") } + if item.StateVersion != _VERSION_1 { + return nil, errors.New("unexpected storage version") + } return deserializeGroup(item.Value) } @@ -339,6 +342,7 @@ func changeRecovery(srvc *native.NativeService) ([]byte, error) { func setOldRecovery(srvc *native.NativeService, encID []byte, recovery common.Address) error { key := append(encID, FIELD_RECOVERY) val := states.StorageItem{Value: recovery[:]} + val.StateVersion = _VERSION_0 srvc.CacheDB.Put(key, val.ToArray()) return nil } @@ -353,5 +357,8 @@ func getOldRecovery(srvc *native.NativeService, encID []byte) ([]byte, error) { } else if item == nil { return nil, nil } + if item.StateVersion != _VERSION_0 { + return nil, errors.New("unexpected storage version") + } return item.Value, nil } diff --git a/smartcontract/service/native/ontid/recovery_test.go b/smartcontract/service/native/ontid/recovery_test.go new file mode 100644 index 0000000000..f5356febba --- /dev/null +++ b/smartcontract/service/native/ontid/recovery_test.go @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2018 The ontology Authors + * This file is part of The ontology library. + * + * The ontology is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ontology is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with The ontology. If not, see . + */ +package ontid + +import ( + "bytes" + "testing" + + "github.com/ontio/ontology-crypto/keypair" + "github.com/ontio/ontology/account" + "github.com/ontio/ontology/common" + "github.com/ontio/ontology/smartcontract/service/native" + "github.com/ontio/ontology/smartcontract/service/native/utils" +) + +func TestRecovery(t *testing.T) { + testcase(t, CaseRecovery) +} + +func CaseRecovery(t *testing.T, n *native.NativeService) { + id0, _ := account.GenerateID() + a0 := account.NewAccount("") + id1, _ := account.GenerateID() + a1 := account.NewAccount("") + id2, _ := account.GenerateID() + a2 := account.NewAccount("") + id3, _ := account.GenerateID() + a3 := account.NewAccount("") + + if regID(n, id0, a0) != nil { + t.Fatal("register id0 error") + } + + g := &Group{ + Threshold: 2, + Members: []interface{}{[]byte(id1), []byte(id2)}, + } + + // 1. set unregistered id as recovery, should fail + if err := setRec(n, id0, g, a0.Address); err == nil { + t.Error("unregistered id is setted as recovery") + } + + // 2. register id + if regID(n, id1, a1) != nil { + t.Fatal("register id1 error") + } + if regID(n, id2, a2) != nil { + t.Fatal("register id2 error") + } + if regID(n, id3, a3) != nil { + t.Fatal("register id3 error") + } + + // 3. set recovery without valid signature, should fail + if err := setRec(n, id0, g, common.ADDRESS_EMPTY); err == nil { + t.Error("recovery setted without valid signature") + } + + // 4. set id1 and id2 as id0's recovery id + if err := setRec(n, id0, g, a0.Address); err != nil { + t.Fatal(err) + } + + // 5. set recovery again, should fail + g = &Group{ + Threshold: 2, + Members: []interface{}{ + []byte(id1), + &Group{ + Threshold: 1, + Members: []interface{}{ + []byte(id2), + []byte(id3), + }, + }, + }, + } + if err := setRec(n, id0, g, a0.Address); err == nil { + t.Error("should not set recovery twice") + } + + // 6. update recovery by invalid recovery, should fail + s := []Signer{ + Signer{[]byte(id0), 1}, + Signer{[]byte(id1), 1}, + } + addr := []common.Address{a0.Address, a1.Address} + if err := updateRec(n, id0, g, s, addr); err == nil { + t.Error("recovery updated by invalid recovery") + } + + // 7. update without enough signature, should fail + s[0].id = []byte(id2) + addr[0] = a2.Address + if err := updateRec(n, id0, g, s, addr[1:]); err == nil { + t.Error("recovery updated without enough signature") + } + + // 8. update without enough signers, should fail + if err := updateRec(n, id0, g, s[1:], addr); err == nil { + t.Error("recovery updated without enough signers") + } + + // 9. update recovery + if err := updateRec(n, id0, g, s, addr); err != nil { + t.Fatal(err) + } + + // 10. add key without enough signer, should fail + a4 := account.NewAccount("") + pk := keypair.SerializePublicKey(a4.PubKey()) + if err := addKeyByRec(n, id0, pk, s[1:], addr); err == nil { + t.Error("add key by recovery without enough signer") + } + + // 11. add key without enough signature, should fail + if err := addKeyByRec(n, id0, pk, s, addr[1:]); err == nil { + t.Error("add key by recovery without enough signature") + } + + // 12. add key by recovery + if err := addKeyByRec(n, id0, pk, s, addr); err != nil { + t.Fatal(err) + } + + // 13. verify added key + sink := common.NewZeroCopySink(nil) + sink.WriteString(id0) + utils.EncodeVarUint(sink, 2) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a4.Address} + res, err := verifySignature(n) + if err != nil || !bytes.Equal(res, utils.BYTE_TRUE) { + t.Fatal("verifying added key failed") + } + + // 14. remove key without enough signer, should fail + if err := rmKeyByRec(n, id0, 2, s[1:], addr); err == nil { + t.Error("key removed by recovery without enought signer") + } + + // 15. remove key without enough signature, should fail + if err := rmKeyByRec(n, id0, 2, s, addr[1:]); err == nil { + t.Error("key removed by recovery without enought signature") + } + + // 16. remove key by recovery + if err := rmKeyByRec(n, id0, 2, s, addr); err != nil { + t.Error(err) + } + + // 17. check removed key + sink.Reset() + sink.WriteString(id0) + utils.EncodeVarUint(sink, 2) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{a4.Address} + res, err = verifySignature(n) + if err == nil && bytes.Equal(res, utils.BYTE_TRUE) { + t.Error("removed key passed verification") + } + + // 18. add the removed key again, should fail + if err := addKeyByRec(n, id0, pk, s, addr); err == nil { + t.Error("removed key should not be added again by recovery") + } +} + +func setRec(n *native.NativeService, id string, g *Group, addr common.Address) error { + sink := common.NewZeroCopySink(nil) + sink.WriteString(id) + sink.WriteVarBytes(g.Serialize()) + utils.EncodeVarUint(sink, 1) + n.Input = sink.Bytes() + n.Tx.SignedAddr = []common.Address{addr} + _, err := setRecovery(n) + return err +} + +func updateRec(n *native.NativeService, id string, g *Group, s []Signer, addr []common.Address) error { + sink := common.NewZeroCopySink(nil) + sink.WriteString(id) + sink.WriteVarBytes(g.Serialize()) + sink.WriteVarBytes(SerializeSigners(s)) + n.Input = sink.Bytes() + n.Tx.SignedAddr = addr + _, err := updateRecovery(n) + return err +} + +func addKeyByRec(n *native.NativeService, id string, pk []byte, s []Signer, addr []common.Address) error { + sink := common.NewZeroCopySink(nil) + sink.WriteString(id) + sink.WriteVarBytes(pk) + sink.WriteVarBytes(SerializeSigners(s)) + n.Input = sink.Bytes() + n.Tx.SignedAddr = addr + _, err := addKeyByRecovery(n) + return err +} + +func rmKeyByRec(n *native.NativeService, id string, index uint64, s []Signer, addr []common.Address) error { + sink := common.NewZeroCopySink(nil) + sink.WriteString(id) + utils.EncodeVarUint(sink, index) + sink.WriteVarBytes(SerializeSigners(s)) + n.Input = sink.Bytes() + n.Tx.SignedAddr = addr + _, err := removeKeyByRecovery(n) + return err +} diff --git a/smartcontract/service/native/ontid/utils.go b/smartcontract/service/native/ontid/utils.go index c1927330e7..3fc0d10759 100644 --- a/smartcontract/service/native/ontid/utils.go +++ b/smartcontract/service/native/ontid/utils.go @@ -91,13 +91,24 @@ func checkWitness(srvc *native.NativeService, key []byte) error { // try as if key is an address addr, err := common.AddressParseFromBytes(key) - if srvc.ContextRef.CheckWitness(addr) { + if err == nil && srvc.ContextRef.CheckWitness(addr) { return nil } return errors.New("check witness failed, " + hex.EncodeToString(key)) } +func checkWitnessByIndex(srvc *native.NativeService, encID []byte, index uint32) error { + pk, err := getPk(srvc, encID, index) + if err != nil { + return err + } else if pk.revoked { + return errors.New("revoked key") + } + + return checkWitness(srvc, pk.key) +} + func deleteID(srvc *native.NativeService, encID []byte) error { key := append(encID, FIELD_PK) srvc.CacheDB.Delete(key) diff --git a/smartcontract/service/native/testsuite/fakedb.go b/smartcontract/service/native/testsuite/fakedb.go new file mode 100644 index 0000000000..148ac2eb0f --- /dev/null +++ b/smartcontract/service/native/testsuite/fakedb.go @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018 The ontology Authors + * This file is part of The ontology library. + * + * The ontology is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ontology is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with The ontology. If not, see . + */ +package testsuite + +import ( + "github.com/ontio/ontology/core/store/common" + "github.com/ontio/ontology/core/store/overlaydb" +) + +type MockDB struct { + common.PersistStore + db map[string]string +} + +func (self *MockDB) Get(key []byte) ([]byte, error) { + val, ok := self.db[string(key)] + if ok == false { + return nil, common.ErrNotFound + } + return []byte(val), nil +} + +func (self *MockDB) BatchPut(key []byte, value []byte) { + self.db[string(key)] = string(value) +} + +func (self *MockDB) BatchDelete(key []byte) { + delete(self.db, string(key)) +} + +func NewOverlayDB() *overlaydb.OverlayDB { + return overlaydb.NewOverlayDB(&MockDB{nil, make(map[string]string)}) +} diff --git a/smartcontract/service/native/testsuite/ont_suite.go b/smartcontract/service/native/testsuite/ont_suite.go new file mode 100644 index 0000000000..47cea7ffd9 --- /dev/null +++ b/smartcontract/service/native/testsuite/ont_suite.go @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2018 The ontology Authors + * This file is part of The ontology library. + * + * The ontology is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ontology is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with The ontology. If not, see . + */ +package testsuite + +import ( + "crypto/rand" + "encoding/hex" + "github.com/stretchr/testify/assert" + "testing" + "time" + + "github.com/ontio/ontology/common" + "github.com/ontio/ontology/core/payload" + "github.com/ontio/ontology/core/types" + utils2 "github.com/ontio/ontology/core/utils" + "github.com/ontio/ontology/smartcontract" + "github.com/ontio/ontology/smartcontract/service/native" + "github.com/ontio/ontology/smartcontract/storage" +) + +func RandomAddress() common.Address { + var addr common.Address + _, _ = rand.Read(addr[:]) + + return addr +} + +func InvokeNativeContract(t *testing.T, addr common.Address, handler native.Handler) { + buf := make([]byte, 100) + _, _ = rand.Read(buf) + method := hex.EncodeToString(buf) + actions := make(map[string]native.Handler) + actions[method] = handler + AppendNativeContract(addr, actions) + + tx := BuildInvokeTx(addr, method, []interface{}{""}) + assert.NotNil(t, tx) + + overlay := NewOverlayDB() + cache := storage.NewCacheDB(overlay) + + _, err := executeTransaction(tx, cache) + + assert.Nil(t, err) +} + +func AppendNativeContract(addr common.Address, actions map[string]native.Handler) { + origin, ok := native.Contracts[addr] + + contract := func(native *native.NativeService) { + if ok { + origin(native) + } + for name, fun := range actions { + native.Register(name, fun) + } + } + native.Contracts[addr] = contract +} + +func executeTransaction(tx *types.Transaction, cache *storage.CacheDB) (interface{}, error) { + config := &smartcontract.Config{ + Time: uint32(time.Now().Unix()), + Tx: tx, + } + + if tx.TxType == types.InvokeNeo { + invoke := tx.Payload.(*payload.InvokeCode) + + sc := smartcontract.SmartContract{ + Config: config, + Store: nil, + CacheDB: cache, + Gas: 100000000000000, + PreExec: true, + } + + //start the smart contract executive function + engine, _ := sc.NewExecuteEngine(invoke.Code, tx.TxType) + res, err := engine.Invoke() + if err != nil { + return nil, err + } + return res, nil + } + + panic("unimplemented") +} + +func BuildInvokeTx(contractAddress common.Address, method string, + args []interface{}) *types.Transaction { + invokCode, err := utils2.BuildNativeInvokeCode(contractAddress, 0, method, args) + if err != nil { + return nil + } + invokePayload := &payload.InvokeCode{ + Code: invokCode, + } + tx := &types.MutableTransaction{ + Version: 0, + GasPrice: 0, + GasLimit: 1000000000, + TxType: types.InvokeNeo, + Nonce: uint32(time.Now().Unix()), + Payload: invokePayload, + Sigs: make([]types.Sig, 0, 0), + } + res, err := tx.IntoImmutable() + if err != nil { + return nil + } + return res +} diff --git a/smartcontract/service/native/testsuite/ont_suite_test.go b/smartcontract/service/native/testsuite/ont_suite_test.go new file mode 100644 index 0000000000..7f732b2e46 --- /dev/null +++ b/smartcontract/service/native/testsuite/ont_suite_test.go @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2018 The ontology Authors + * This file is part of The ontology library. + * + * The ontology is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The ontology is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with The ontology. If not, see . + */ +package testsuite + +import ( + "github.com/ontio/ontology/common" + "github.com/ontio/ontology/smartcontract/service/native" + _ "github.com/ontio/ontology/smartcontract/service/native/init" + "github.com/ontio/ontology/smartcontract/service/native/ont" + "github.com/ontio/ontology/smartcontract/service/native/utils" + "github.com/ontio/ontology/smartcontract/storage" + "github.com/stretchr/testify/assert" + + "testing" +) + +func setOntBalance(db *storage.CacheDB, addr common.Address, value uint64) { + balanceKey := ont.GenBalanceKey(utils.OntContractAddress, addr) + item := utils.GenUInt64StorageItem(value) + db.Put(balanceKey, item.ToArray()) +} + +func ontBalanceOf(native *native.NativeService, addr common.Address) int { + sink := common.NewZeroCopySink(nil) + utils.EncodeAddress(sink, addr) + native.Input = sink.Bytes() + buf, _ := ont.OntBalanceOf(native) + val := common.BigIntFromNeoBytes(buf) + return int(val.Uint64()) +} + +func ontTransfer(native *native.NativeService, from, to common.Address, value uint64) error { + native.Tx.SignedAddr = append(native.Tx.SignedAddr, from) + + state := ont.State{from, to, value} + native.Input = common.SerializeToBytes(&ont.Transfers{States: []ont.State{state}}) + + _, err := ont.OntTransfer(native) + return err +} + +func TestTransfer(t *testing.T) { + InvokeNativeContract(t, utils.OntContractAddress, func(native *native.NativeService) ([]byte, error) { + a := RandomAddress() + b := RandomAddress() + c := RandomAddress() + setOntBalance(native.CacheDB, a, 10000) + + assert.Equal(t, ontBalanceOf(native, a), 10000) + assert.Equal(t, ontBalanceOf(native, b), 0) + assert.Equal(t, ontBalanceOf(native, c), 0) + + assert.Nil(t, ontTransfer(native, a, b, 10)) + assert.Equal(t, ontBalanceOf(native, a), 9990) + assert.Equal(t, ontBalanceOf(native, b), 10) + + assert.Nil(t, ontTransfer(native, b, c, 10)) + assert.Equal(t, ontBalanceOf(native, b), 0) + assert.Equal(t, ontBalanceOf(native, c), 10) + + return nil, nil + }) +}