From edfab37677949ebaacaacc3927ee1bdf551d3f92 Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Tue, 7 Nov 2023 13:54:48 +0300 Subject: [PATCH] [#48] frostfsid: Update contract Signed-off-by: Denis Kirillov --- frostfsid/config.yml | 87 ++- frostfsid/doc.go | 30 +- frostfsid/frostfsid_contract.go | 917 ++++++++++++++++++++++++++++---- 3 files changed, 909 insertions(+), 125 deletions(-) diff --git a/frostfsid/config.yml b/frostfsid/config.yml index a8daad2..90b7311 100644 --- a/frostfsid/config.yml +++ b/frostfsid/config.yml @@ -1,4 +1,89 @@ name: "Identity" -safemethods: ["key", "version"] +safemethods: ["version"] permissions: - methods: ["update"] +events: + - name: CreateSubject + parameters: + - name: subjectAddress + type: Hash160 + - name: AddSubjectKey + parameters: + - name: subjectAddress + type: Hash160 + - name: subjectKey + type: PublicKey + - name: RemoveSubjectKey + parameters: + - name: subjectAddress + type: Hash160 + - name: subjectKey + type: PublicKey + - name: SetSubjectName + parameters: + - name: subjectAddress + type: Hash160 + - name: name + type: String + - name: SetSubjectKV + parameters: + - name: subjectAddress + type: Hash160 + - name: key + type: String + - name: value + type: String + - name: DeleteSubjectKV + parameters: + - name: subjectAddress + type: Hash160 + - name: key + type: String + - name: DeleteSubject + parameters: + - name: subjectAddress + type: Hash160 + - name: CreateNamespace + parameters: + - name: namespace + type: String + - name: AddSubjectToNamespace + parameters: + - name: subjectAddress + type: Hash160 + - name: namespace + type: String + - name: RemoveSubjectFromNamespace + parameters: + - name: subjectAddress + type: Hash160 + - name: namespace + type: String + - name: CreateGroup + parameters: + - name: namespace + type: String + - name: group + type: String + - name: AddSubjectToGroup + parameters: + - name: subjectAddress + type: Hash160 + - name: namespace + type: String + - name: group + type: String + - name: RemoveSubjectFromGroup + parameters: + - name: subjectAddress + type: Hash160 + - name: namespace + type: String + - name: group + type: String + - name: DeleteGroup + parameters: + - name: namespace + type: String + - name: group + type: String diff --git a/frostfsid/doc.go b/frostfsid/doc.go index 9efa8f4..2b15bfa 100644 --- a/frostfsid/doc.go +++ b/frostfsid/doc.go @@ -1,28 +1,24 @@ +// Package frostfsid /* FrostFSID contract is a contract deployed in FrostFS sidechain. -FrostFSID contract is used to store connection between an OwnerID and its public keys. -OwnerID is a 25-byte N3 wallet address that can be produced from a public key. -It is one-way conversion. In simple cases, FrostFS verifies ownership by checking -signature and relation between a public key and an OwnerID. - -In more complex cases, a user can use public keys unrelated to the OwnerID to maintain -secure access to the data. FrostFSID contract stores relation between an OwnerID and -arbitrary public keys. Data owner can bind a public key with its account or unbind it -by invoking Bind or Unbind methods of FrostFS contract in the mainchain. After that, -Alphabet nodes produce multisigned AddKey and RemoveKey invocations of FrostFSID -contract. - # Contract notifications FrostFSID contract does not produce notifications to process. # Contract storage scheme - | Key | Value | Description | - |-----------------------------|------------|----------------------------------| - | `processingScriptHash` | Hash160 | netmap contract hash | - | `containerScriptHash` | Hash160 | container contract hash | - | `o` + ownerID + publicKey | ByteArray | it flags owner's public key | + | Key | Value | Description | + |---------------------------------------------------------------------------------|--------------------------------|----------------------------------------------------| + | `o` + [ owner address ] | []byte{1} | contract owners that can invoke write methods | + | `s` + [ subject address ] | Serialized Subject structure | subject into | + | `a` + [ pk address ] + [ subject address ] | []byte{1} | link extra public keys for subject | + | `n` + [ RIPEMD160 of namespace ] | Serialized Namespace structure | namespace info | + | `N` + [ RIPEMD160 of namespace ] + [ subject address ] | []byte{1} | subject that belongs to the namespace | + | `l` + [ RIPEMD160 of namespace ] + [ RIPEMD160 of subject name ] | Subject public key | subject name to public key index | + | `g` + [ RIPEMD160 of namespace ] + [ RIPEMD160 of group ] | Serialized Group structure | group into | + | `G` + [ RIPEMD160 of namespace ] + [ RIPEMD160 of group ] + [ subject address ] | []byte{1} | subject that belongs to the group | + + */ package frostfsid diff --git a/frostfsid/frostfsid_contract.go b/frostfsid/frostfsid_contract.go index d736863..6d60b9a 100644 --- a/frostfsid/frostfsid_contract.go +++ b/frostfsid/frostfsid_contract.go @@ -3,52 +3,106 @@ package frostfsid import ( "git.frostfs.info/TrueCloudLab/frostfs-contract/common" "github.com/nspcc-dev/neo-go/pkg/interop" + "github.com/nspcc-dev/neo-go/pkg/interop/contract" "github.com/nspcc-dev/neo-go/pkg/interop/iterator" + "github.com/nspcc-dev/neo-go/pkg/interop/native/crypto" "github.com/nspcc-dev/neo-go/pkg/interop/native/management" + "github.com/nspcc-dev/neo-go/pkg/interop/native/std" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" ) type ( - UserInfo struct { - Keys [][]byte + Subject struct { + PrimaryKey interop.PublicKey + AdditionalKeys []interop.PublicKey + Namespace string + Name string + KV map[string]string + } + + SubjectExtended struct { + PrimaryKey interop.PublicKey + AdditionalKeys []interop.PublicKey + Namespace string + Name string + KV map[string]string + Groups []Group + } + + Namespace struct { + Name string + } + + NamespaceExtended struct { + Name string + GroupsCount int + SubjectsCount int + } + + Group struct { + Name string + Namespace string + } + + GroupExtended struct { + Name string + Namespace string + SubjectsCount int } ) const ( - ownerSize = 1 + interop.Hash160Len + 4 -) - -const ( - netmapContractKey = "netmapScriptHash" - containerContractKey = "containerScriptHash" - ownerKeysPrefix = 'o' + ownerKeysPrefix = 'o' + subjectKeysPrefix = 's' + additionalKeysPrefix = 'a' + namespaceKeysPrefix = 'n' + namespaceSubjectsKeysPrefix = 'N' + namespaceSubjectsNamesPrefix = 'l' + groupKeysPrefix = 'g' + groupSubjectsKeysPrefix = 'G' ) func _deploy(data any, isUpdate bool) { ctx := storage.GetContext() - if isUpdate { - args := data.([]any) - common.CheckVersion(args[len(args)-1].(int)) - return - } - args := data.(struct { - addrNetmap interop.Hash160 - addrContainer interop.Hash160 + owners []interop.Hash160 }) - if len(args.addrNetmap) != interop.Hash160Len || len(args.addrContainer) != interop.Hash160Len { - panic("incorrect length of contract script hash") + for _, owner := range args.owners { + if len(owner) != interop.Hash160Len { + panic("incorrect length of owner addresses") + } + storage.Put(ctx, ownerKey(owner), []byte{1}) } - storage.Put(ctx, netmapContractKey, args.addrNetmap) - storage.Put(ctx, containerContractKey, args.addrContainer) - runtime.Log("frostfsid contract initialized") } +func AddOwner(addr interop.Hash160) { + ctx := storage.GetContext() + if !common.HasUpdateAccess() { + panic("not witnessed") + } + + storage.Put(ctx, ownerKey(addr), []byte{1}) +} + +func DeleteOwner(addr interop.Hash160) { + ctx := storage.GetContext() + if !common.HasUpdateAccess() { + panic("not witnessed") + } + + storage.Delete(ctx, ownerKey(addr)) +} + +func ListOwners() iterator.Iterator { + ctx := storage.GetReadOnlyContext() + return storage.Find(ctx, []byte{ownerKeysPrefix}, storage.KeysOnly|storage.RemovePrefix) +} + // Update method updates contract source code and manifest. It can be invoked // only by committee. func Update(script []byte, manifest []byte, data any) { @@ -60,96 +114,745 @@ func Update(script []byte, manifest []byte, data any) { runtime.Log("frostfsid contract updated") } -// AddKey binds a list of the provided public keys to the OwnerID. It can be invoked only by -// Alphabet nodes. -// -// This method panics if the OwnerID is not an ownerSize byte or the public key is not 33 byte long. -// If the key is already bound, the method ignores it. -func AddKey(owner []byte, keys []interop.PublicKey) { - // V2 format - if len(owner) != ownerSize { - panic("incorrect owner") - } - - for i := range keys { - if len(keys[i]) != interop.PublicKeyCompressedLen { - panic("incorrect public key") - } - } - - ctx := storage.GetContext() - - common.CheckAlphabetWitness() - - ownerKey := append([]byte{ownerKeysPrefix}, owner...) - for i := range keys { - stKey := append(ownerKey, keys[i]...) - storage.Put(ctx, stKey, []byte{1}) - } - - runtime.Log("key bound to the owner") -} - -// RemoveKey unbinds the provided public keys from the OwnerID. It can be invoked only by -// Alphabet nodes. -// -// This method panics if the OwnerID is not an ownerSize byte or the public key is not 33 byte long. -// If the key is already unbound, the method ignores it. -func RemoveKey(owner []byte, keys []interop.PublicKey) { - // V2 format - if len(owner) != ownerSize { - panic("incorrect owner") - } - - for i := range keys { - if len(keys[i]) != interop.PublicKeyCompressedLen { - panic("incorrect public key") - } - } - - ctx := storage.GetContext() - - multiaddr := common.AlphabetAddress() - if !runtime.CheckWitness(multiaddr) { - panic("invocation from non inner ring node") - } - - ownerKey := append([]byte{ownerKeysPrefix}, owner...) - for i := range keys { - stKey := append(ownerKey, keys[i]...) - storage.Delete(ctx, stKey) - } -} - -// Key method returns a list of 33-byte public keys bound with the OwnerID. -// -// This method panics if the owner is not ownerSize byte long. -func Key(owner []byte) [][]byte { - // V2 format - if len(owner) != ownerSize { - panic("incorrect owner") - } - - ctx := storage.GetReadOnlyContext() - - ownerKey := append([]byte{ownerKeysPrefix}, owner...) - info := getUserInfo(ctx, ownerKey) - - return info.Keys -} - // Version returns the version of the contract. func Version() int { return common.Version } -func getUserInfo(ctx storage.Context, key any) UserInfo { - it := storage.Find(ctx, key, storage.KeysOnly|storage.RemovePrefix) - pubs := [][]byte{} - for iterator.Next(it) { - pub := iterator.Value(it).([]byte) - pubs = append(pubs, pub) +func CreateSubject(key interop.PublicKey) { + ctx := storage.GetContext() + checkContractOwner(ctx) + + if len(key) != interop.PublicKeyCompressedLen { + panic("incorrect public key length") } - return UserInfo{Keys: pubs} + addr := contract.CreateStandardAccount(key) + sKey := subjectKeyFromAddr(addr) + data := storage.Get(ctx, sKey).([]byte) + if data != nil { + panic("subject already exists") + } + + saPrefix := subjectAdditionalPrefix(key) + it := storage.Find(ctx, saPrefix, storage.KeysOnly) + for iterator.Next(it) { + panic("key is occupied") + } + + subj := Subject{ + PrimaryKey: key, + } + + storage.Put(ctx, sKey, std.Serialize(subj)) + runtime.Notify("CreateSubject", interop.Hash160(addr)) +} + +func AddSubjectKey(addr interop.Hash160, key interop.PublicKey) { + ctx := storage.GetContext() + checkContractOwner(ctx) + + if len(addr) != interop.Hash160Len { + panic("incorrect address length") + } + if len(key) != interop.PublicKeyCompressedLen { + panic("incorrect public key length") + } + + saKey := subjectAdditionalKey(key, addr) + data := storage.Get(ctx, saKey).([]byte) + if data != nil { + panic("key already added") + } + + storage.Put(ctx, saKey, []byte{1}) + + sKey := subjectKeyFromAddr(addr) + data = storage.Get(ctx, sKey).([]byte) + if data == nil { + panic("address not found") + } + subject := std.Deserialize(data).(Subject) + subject.AdditionalKeys = append(subject.AdditionalKeys, key) + + storage.Put(ctx, sKey, std.Serialize(subject)) + runtime.Notify("AddSubjectKey", addr, key) +} + +func RemoveSubjectKey(addr interop.Hash160, key interop.PublicKey) { + ctx := storage.GetContext() + checkContractOwner(ctx) + + if len(addr) != interop.Hash160Len { + panic("incorrect address length") + } + if len(key) != interop.PublicKeyCompressedLen { + panic("incorrect public key length") + } + + saKey := subjectAdditionalKey(key, addr) + data := storage.Get(ctx, saKey).([]byte) + if data == nil { + panic("key already removed") + } + + storage.Delete(ctx, saKey) + + sKey := subjectKeyFromAddr(addr) + data = storage.Get(ctx, sKey).([]byte) + if data == nil { + panic("address not found") + } + subject := std.Deserialize(data).(Subject) + + var additionalKeys []interop.PublicKey + for i := 0; i < len(subject.AdditionalKeys); i++ { + if !common.BytesEqual(subject.AdditionalKeys[i], key) { + additionalKeys = append(additionalKeys, subject.AdditionalKeys[i]) + } + } + subject.AdditionalKeys = additionalKeys + + storage.Put(ctx, sKey, std.Serialize(subject)) + runtime.Notify("RemoveSubjectKey", addr, key) +} + +func SetSubjectName(addr interop.Hash160, name string) { + ctx := storage.GetContext() + checkContractOwner(ctx) + + if len(addr) != interop.Hash160Len { + panic("incorrect address length") + } + + sKey := subjectKeyFromAddr(addr) + data := storage.Get(ctx, sKey).([]byte) + if data == nil { + panic("subject not found") + } + + subject := std.Deserialize(data).(Subject) + oldName := subject.Name + subject.Name = name + storage.Put(ctx, sKey, std.Serialize(subject)) + + updateNamespaceSubjectName(ctx, subject, oldName) + + runtime.Notify("SetSubjectName", addr, name) +} + +func SetSubjectKV(addr interop.Hash160, key, val string) { + ctx := storage.GetContext() + checkContractOwner(ctx) + + if len(addr) != interop.Hash160Len { + panic("incorrect address length") + } + + sKey := subjectKeyFromAddr(addr) + data := storage.Get(ctx, sKey).([]byte) + if data == nil { + panic("subject not found") + } + + subject := std.Deserialize(data).(Subject) + if subject.KV == nil { + subject.KV = map[string]string{} + } + subject.KV[key] = val + + storage.Put(ctx, sKey, std.Serialize(subject)) + runtime.Notify("SetSubjectKV", addr, key, val) +} + +func DeleteSubjectKV(addr interop.Hash160, key string) { + ctx := storage.GetContext() + checkContractOwner(ctx) + + if len(addr) != interop.Hash160Len { + panic("incorrect address length") + } + + sKey := subjectKeyFromAddr(addr) + data := storage.Get(ctx, sKey).([]byte) + if data == nil { + panic("subject not found") + } + + subject := std.Deserialize(data).(Subject) + delete(subject.KV, key) + + storage.Put(ctx, sKey, std.Serialize(subject)) + runtime.Notify("DeleteSubjectKV", addr, key) +} + +func DeleteSubject(addr interop.Hash160) { + ctx := storage.GetContext() + checkContractOwner(ctx) + + if len(addr) != interop.Hash160Len { + panic("incorrect address length") + } + + sKey := subjectKeyFromAddr(addr) + data := storage.Get(ctx, sKey).([]byte) + if data == nil { + return + } + subj := std.Deserialize(data).(Subject) + + for i := 0; i < len(subj.AdditionalKeys); i++ { + storage.Delete(ctx, subjectAdditionalKey(subj.AdditionalKeys[i], addr)) + } + storage.Delete(ctx, sKey) + + if subj.Namespace != "" { + removeSubjectFromNamespace(ctx, subj.Namespace, addr) + } + deleteNamespaceSubjectName(ctx, subj.Namespace, subj.Name) + + runtime.Notify("DeleteSubject", addr) +} + +func GetSubject(addr interop.Hash160) Subject { + if len(addr) != interop.Hash160Len { + panic("incorrect address length") + } + + ctx := storage.GetReadOnlyContext() + sKey := subjectKeyFromAddr(addr) + data := storage.Get(ctx, sKey).([]byte) + if data == nil { + panic("subject not found") + } + + return std.Deserialize(data).(Subject) +} + +func GetSubjectExtended(addr interop.Hash160) SubjectExtended { + if len(addr) != interop.Hash160Len { + panic("incorrect address length") + } + + ctx := storage.GetReadOnlyContext() + sKey := subjectKeyFromAddr(addr) + data := storage.Get(ctx, sKey).([]byte) + if data == nil { + panic("subject not found") + } + + subj := std.Deserialize(data).(Subject) + + var groups []Group + if subj.Namespace != "" { + nsHash := ripemd160Hash(subj.Namespace) + it := storage.Find(ctx, groupPrefixFromHash(nsHash), storage.KeysOnly|storage.RemovePrefix) + for iterator.Next(it) { + groupHash := iterator.Value(it).([]byte) + data = storage.Get(ctx, groupSubjectKeyFromHashes(nsHash, groupHash, addr)).([]byte) + if data != nil { + data = storage.Get(ctx, groupKeyFromHashes(nsHash, groupHash)).([]byte) + if data == nil { + panic("group not found") + } + groups = append(groups, std.Deserialize(data).(Group)) + } + } + } + + subjExt := SubjectExtended{ + PrimaryKey: subj.PrimaryKey, + AdditionalKeys: subj.AdditionalKeys, + Namespace: subj.Namespace, + Name: subj.Name, + KV: subj.KV, + Groups: groups, + } + + return subjExt +} + +func GetSubjectByKey(key interop.PublicKey) Subject { + if len(key) != interop.PublicKeyCompressedLen { + panic("incorrect key length") + } + + ctx := storage.GetReadOnlyContext() + + sKey := subjectKey(key) + data := storage.Get(ctx, sKey).([]byte) + if data != nil { + return std.Deserialize(data).(Subject) + } + + saPrefix := subjectAdditionalPrefix(key) + it := storage.Find(ctx, saPrefix, storage.KeysOnly|storage.RemovePrefix) + for iterator.Next(it) { + addr := iterator.Value(it).([]byte) + sKey = subjectKeyFromAddr(addr) + data = storage.Get(ctx, sKey).([]byte) + if data != nil { + return std.Deserialize(data).(Subject) + } + break + } + + panic("subject not found") +} + +func GetSubjectKeyByName(ns, name string) interop.PublicKey { + if ns == "" || name == "" { + panic("invalid namespace or name") + } + + ctx := storage.GetReadOnlyContext() + + nsSubjNameKey := namespaceSubjectNameKey(ns, name) + subjKey := storage.Get(ctx, nsSubjNameKey).(interop.PublicKey) + if subjKey == nil { + panic("subject not found") + } + + return subjKey +} + +func ListSubjects() iterator.Iterator { + ctx := storage.GetReadOnlyContext() + return storage.Find(ctx, []byte{subjectKeysPrefix}, storage.KeysOnly|storage.RemovePrefix) +} + +func CreateNamespace(ns string) { + ctx := storage.GetContext() + checkContractOwner(ctx) + + if ns == "" { + panic("invalid namespace name") + } + + nsKey := namespaceKey(ns) + data := storage.Get(ctx, nsKey).([]byte) + if data != nil { + panic("namespace already exists") + } + + namespace := Namespace{ + Name: ns, + } + + storage.Put(ctx, nsKey, std.Serialize(namespace)) + runtime.Notify("CreateNamespace", ns) +} + +func GetNamespace(ns string) Namespace { + ctx := storage.GetReadOnlyContext() + nsKey := namespaceKey(ns) + data := storage.Get(ctx, nsKey).([]byte) + if data == nil { + panic("namespace not found") + } + + return std.Deserialize(data).(Namespace) +} + +func GetNamespaceExtended(ns string) NamespaceExtended { + ctx := storage.GetReadOnlyContext() + nsKey := namespaceKey(ns) + data := storage.Get(ctx, nsKey).([]byte) + if data == nil { + panic("namespace not found") + } + namespace := std.Deserialize(data).(Namespace) + + nsExtended := NamespaceExtended{ + Name: namespace.Name, + } + + it := storage.Find(ctx, groupPrefix(ns), storage.KeysOnly) + for iterator.Next(it) { + nsExtended.GroupsCount += 1 + } + + it = storage.Find(ctx, namespaceSubjectPrefix(ns), storage.KeysOnly) + for iterator.Next(it) { + nsExtended.SubjectsCount += 1 + } + + return nsExtended +} + +func ListNamespaces() iterator.Iterator { + ctx := storage.GetReadOnlyContext() + return storage.Find(ctx, []byte{namespaceKeysPrefix}, storage.ValuesOnly|storage.DeserializeValues) +} + +func AddSubjectToNamespace(addr interop.Hash160, ns string) { + ctx := storage.GetContext() + checkContractOwner(ctx) + + if ns == "" { + panic("invalid namespace name") + } + + if len(addr) != interop.Hash160Len { + panic("invalid address length") + } + + sKey := subjectKeyFromAddr(addr) + data := storage.Get(ctx, sKey).([]byte) + if data == nil { + panic("subject not found") + } + subject := std.Deserialize(data).(Subject) + + if subject.Namespace == ns { + panic("subject already added") + } + if subject.Namespace != "" { + panic("subject cannot be moved to another namespace") + } + + subject.Namespace = ns + storage.Put(ctx, sKey, std.Serialize(subject)) + + data = storage.Get(ctx, namespaceKey(ns)).([]byte) + if data == nil { + panic("namespace not found") + } + + nsSubjKey := namespaceSubjectKey(ns, addr) + storage.Put(ctx, nsSubjKey, []byte{1}) + + setNamespaceSubjectName(ctx, subject) + + runtime.Notify("AddSubjectToNamespace", addr, ns) +} + +func RemoveSubjectFromNamespace(addr interop.Hash160) { + ctx := storage.GetContext() + checkContractOwner(ctx) + + if len(addr) != interop.Hash160Len { + panic("invalid address length") + } + + sKey := subjectKeyFromAddr(addr) + data := storage.Get(ctx, sKey).([]byte) + if data == nil { + panic("subject not found") + } + subject := std.Deserialize(data).(Subject) + + if subject.Namespace == "" { + panic("subject does not belong to any namespace") + } + + removeSubjectFromNamespace(ctx, subject.Namespace, addr) + deleteNamespaceSubjectName(ctx, subject.Namespace, subject.Name) + + subject.Namespace = "" + storage.Put(ctx, sKey, std.Serialize(subject)) + + runtime.Notify("RemoveSubjectFromNamespace", addr, subject.Namespace) +} + +func ListNamespaceSubjects(ns string) iterator.Iterator { + ctx := storage.GetReadOnlyContext() + return storage.Find(ctx, namespaceSubjectPrefix(ns), storage.KeysOnly|storage.RemovePrefix) +} + +func CreateGroup(ns, group string) { + ctx := storage.GetContext() + checkContractOwner(ctx) + + if ns == "" { + panic("invalid namespace name") + } + if group == "" { + panic("invalid group name") + } + + nsKey := namespaceKey(ns) + data := storage.Get(ctx, nsKey).([]byte) + if data == nil { + panic("namespace not found") + } + + gr := Group{ + Name: group, + Namespace: ns, + } + gKey := groupKey(ns, group) + data = storage.Get(ctx, gKey).([]byte) + if data != nil { + panic("group already exists") + } + storage.Put(ctx, gKey, std.Serialize(gr)) + + runtime.Notify("CreateGroup", ns, group) +} + +func GetGroup(ns, group string) Group { + ctx := storage.GetReadOnlyContext() + gKey := groupKey(ns, group) + data := storage.Get(ctx, gKey).([]byte) + if data == nil { + panic("group not found") + } + + return std.Deserialize(data).(Group) +} + +func GetGroupExtended(ns, group string) GroupExtended { + ctx := storage.GetReadOnlyContext() + gKey := groupKey(ns, group) + data := storage.Get(ctx, gKey).([]byte) + if data == nil { + panic("group not found") + } + + gr := std.Deserialize(data).(Group) + + grExtended := GroupExtended{ + Name: gr.Name, + Namespace: gr.Namespace, + } + + it := storage.Find(ctx, groupSubjectPrefix(ns, group), storage.KeysOnly) + for iterator.Next(it) { + grExtended.SubjectsCount += 1 + } + + return grExtended +} + +func ListGroups(ns string) iterator.Iterator { + ctx := storage.GetReadOnlyContext() + return storage.Find(ctx, groupPrefix(ns), storage.ValuesOnly|storage.DeserializeValues) +} + +func AddSubjectToGroup(addr interop.Hash160, group string) { + ctx := storage.GetContext() + checkContractOwner(ctx) + + if len(addr) != interop.Hash160Len { + panic("invalid address length") + } + + sKey := subjectKeyFromAddr(addr) + data := storage.Get(ctx, sKey).([]byte) + if data == nil { + panic("subject not found") + } + subject := std.Deserialize(data).(Subject) + + if subject.Namespace == "" { + panic("subject does not belong to any namespace") + } + + nSubjKey := namespaceSubjectKey(subject.Namespace, addr) + storage.Put(ctx, nSubjKey, []byte{1}) + + gKey := groupKey(subject.Namespace, group) + data = storage.Get(ctx, gKey).([]byte) + if data == nil { + panic("group not found") + } + + gsKey := groupSubjectKey(subject.Namespace, group, addr) + storage.Put(ctx, gsKey, []byte{1}) + + runtime.Notify("AddSubjectToGroup", addr, subject.Namespace, group) +} + +func RemoveSubjectFromGroup(addr interop.Hash160, ns, group string) { + ctx := storage.GetContext() + checkContractOwner(ctx) + + if len(addr) != interop.Hash160Len { + panic("invalid address length") + } + + sKey := subjectKeyFromAddr(addr) + data := storage.Get(ctx, sKey).([]byte) + if data == nil { + panic("subject not found") + } + + gKey := groupKey(ns, group) + data = storage.Get(ctx, gKey).([]byte) + if data == nil { + panic("group not found") + } + + gsKey := groupSubjectKey(ns, group, addr) + storage.Delete(ctx, gsKey) + + runtime.Notify("RemoveSubjectFromGroup", addr, ns, group) +} + +func ListGroupSubjects(ns, group string) iterator.Iterator { + ctx := storage.GetReadOnlyContext() + return storage.Find(ctx, groupSubjectPrefix(ns, group), storage.KeysOnly|storage.RemovePrefix) +} + +func DeleteGroup(ns, group string) { + ctx := storage.GetContext() + checkContractOwner(ctx) + + gKey := groupKey(ns, group) + data := storage.Get(ctx, gKey).([]byte) + if data == nil { + panic("group not found") + } + storage.Delete(ctx, gKey) + + it := storage.Find(ctx, groupSubjectPrefix(ns, group), storage.KeysOnly) + for iterator.Next(it) { + gsKey := iterator.Value(it).([]byte) + storage.Delete(ctx, gsKey) + } + + runtime.Notify("DeleteGroup", ns, group) +} + +func checkContractOwner(ctx storage.Context) { + it := storage.Find(ctx, []byte{ownerKeysPrefix}, storage.KeysOnly|storage.RemovePrefix) + for iterator.Next(it) { + owner := iterator.Value(it).([]byte) + if runtime.CheckWitness(owner) { + return + } + } + panic("not witnessed") +} + +func removeSubjectFromNamespace(ctx storage.Context, ns string, addr interop.Hash160) { + nsSubjKey := namespaceSubjectKey(ns, addr) + storage.Delete(ctx, nsSubjKey) + + nsHash := ripemd160Hash(ns) + it := storage.Find(ctx, groupPrefixFromHash(nsHash), storage.KeysOnly|storage.RemovePrefix) + for iterator.Next(it) { + groupHash := iterator.Value(it).([]byte) + storage.Delete(ctx, groupSubjectKeyFromHashes(nsHash, groupHash, addr)) + } +} + +func setNamespaceSubjectName(ctx storage.Context, subj Subject) { + if subj.Name == "" { + return + } + + nsSubjNameKey := namespaceSubjectNameKey(subj.Namespace, subj.Name) + subjKey := storage.Get(ctx, nsSubjNameKey).(interop.PublicKey) + if subjKey == nil { + storage.Put(ctx, nsSubjNameKey, subj.PrimaryKey) + } else if !common.BytesEqual(subjKey, subj.PrimaryKey) { + panic("subject name is not available in the current namespace") + } +} + +func deleteNamespaceSubjectName(ctx storage.Context, ns, subjName string) { + if subjName == "" { + return + } + + nsSubjNameKey := namespaceSubjectNameKey(ns, subjName) + storage.Delete(ctx, nsSubjNameKey) +} + +func updateNamespaceSubjectName(ctx storage.Context, subj Subject, oldName string) { + if subj.Name == oldName { + return + } + + deleteNamespaceSubjectName(ctx, subj.Namespace, oldName) + setNamespaceSubjectName(ctx, subj) +} + +func ownerKey(owner interop.Hash160) []byte { + return append([]byte{ownerKeysPrefix}, owner...) +} + +func subjectKey(key interop.PublicKey) []byte { + addr := contract.CreateStandardAccount(key) + return subjectKeyFromAddr(addr) +} + +func subjectKeyFromAddr(addr interop.Hash160) []byte { + return append([]byte{subjectKeysPrefix}, addr...) +} + +func subjectAdditionalKey(additionalKey interop.PublicKey, primeAddr interop.Hash160) []byte { + return append(subjectAdditionalPrefix(additionalKey), primeAddr...) +} + +func subjectAdditionalPrefix(additionalKey interop.PublicKey) []byte { + addr := contract.CreateStandardAccount(additionalKey) + return append([]byte{additionalKeysPrefix}, addr...) +} + +func namespaceKey(ns string) []byte { + return namespaceKeyFromHash(ripemd160Hash(ns)) +} +func namespaceKeyFromHash(ns []byte) []byte { + return append([]byte{namespaceKeysPrefix}, ns...) +} + +func groupKey(ns, group string) []byte { + prefix := groupPrefix(ns) + return append(prefix, ripemd160Hash(group)...) +} + +func groupKeyFromHashes(nsHash, groupHash []byte) []byte { + prefix := groupPrefixFromHash(nsHash) + return append(prefix, groupHash...) +} + +func groupPrefix(ns string) []byte { + return groupPrefixFromHash(ripemd160Hash(ns)) +} + +func groupPrefixFromHash(nsHash []byte) []byte { + return append([]byte{groupKeysPrefix}, nsHash...) +} + +func ripemd160Hash(data string) []byte { + return crypto.Ripemd160([]byte(data)) +} + +func namespaceSubjectKey(ns string, addr interop.Hash160) []byte { + prefix := namespaceSubjectPrefix(ns) + return append(prefix, addr...) +} + +func namespaceSubjectPrefix(ns string) []byte { + nsHash := ripemd160Hash(ns) + return append([]byte{namespaceSubjectsKeysPrefix}, nsHash...) +} + +func namespaceSubjectNameKey(ns, subjName string) []byte { + nsHash := ripemd160Hash(ns) + nameHash := ripemd160Hash(subjName) + return append([]byte{namespaceSubjectsNamesPrefix}, append(nsHash, nameHash...)...) +} + +func groupSubjectKey(ns, group string, addr interop.Hash160) []byte { + prefix := groupSubjectPrefix(ns, group) + return append(prefix, addr...) +} + +func groupSubjectKeyFromHashes(nsHash, groupHash []byte, addr interop.Hash160) []byte { + prefix := groupSubjectPrefixFromHashes(nsHash, groupHash) + return append(prefix, addr...) +} + +func groupSubjectPrefix(ns, group string) []byte { + nsHash := ripemd160Hash(ns) + gHash := ripemd160Hash(group) + return groupSubjectPrefixFromHashes(nsHash, gHash) +} + +func groupSubjectPrefixFromHashes(nsHash, groupHash []byte) []byte { + return append([]byte{groupSubjectsKeysPrefix}, append(nsHash, groupHash...)...) }