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
+ })
+}