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/convert" "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 ( // Subject represents a subject entity. // // Fields: // - Namespace: a string representing the namespace of the subject. // - Name: a string representing the name of the subject. // The name must match the following regex pattern: ^[\w+=,.@-]{1,64}$ // The Subject is stored in the storage as a hash(namespace) + hash(name). 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 represents a namespace. // // Fields: // - Name: a string representing the name of the namespace. // The custom name must match the following regex pattern: // (^$)|(^[a-z0-9]{1,2}$)|(^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$) // An empty Name is considered the namespace. // // The Namespace is stored in the storage as a hash(name). Namespace struct { Name string } NamespaceExtended struct { Name string GroupsCount int SubjectsCount int } // Group represents a group entity. // // Fields: // - ID: an integer representing the unique identifier of the group. // The ID is generated upon creation and is the sequential number of the group within the namespace. // It cannot be changed once set. // - Name: a string representing the name of the group. // The name must match the following regex pattern: [\w+=,.@-]{1,128}$ // - Namespace: a string representing the namespace of the group. // A group exists only within one namespace. // // The group is stored in the storage as a hash(namespace) + hash(name). Group struct { ID int Name string Namespace string KV map[string]string } GroupExtended struct { ID int Name string Namespace string KV map[string]string SubjectsCount int } ) const ( adminKey = 'o' subjectKeysPrefix = 's' additionalKeysPrefix = 'a' namespaceKeysPrefix = 'n' namespaceSubjectsKeysPrefix = 'N' namespaceSubjectsNamesPrefix = 'l' groupKeysPrefix = 'g' groupSubjectsKeysPrefix = 'G' groupCounterKey = 'c' namespaceGroupsNamesPrefix = 'm' addressPrefix = 'A' ) func _deploy(data any, isUpdate bool) { ctx := storage.GetContext() args := data.(struct { admin interop.Hash160 version int }) if args.admin != nil { if len(args.admin) != interop.Hash160Len { panic("incorrect length of owner address") } storage.Put(ctx, adminKey, args.admin) } if isUpdate { common.CheckVersion(args.version) it := storage.Find(ctx, subjectKeysPrefix, storage.ValuesOnly) for iterator.Next(it) { subjectRaw := iterator.Value(it) subject := std.Deserialize(subjectRaw.([]byte)).(Subject) address := addressKey(contract.CreateStandardAccount(subject.PrimaryKey)) if storage.Get(ctx, address) != nil { panic("frostfsid contract contains duplicate keys") } storage.Put(ctx, address, true) for i := 0; i < len(subject.AdditionalKeys); i++ { address = addressKey(contract.CreateStandardAccount(subject.AdditionalKeys[i])) if storage.Get(ctx, address) != nil { panic("frostfsid contract contains duplicate keys") } storage.Put(ctx, address, true) } } } storage.Put(ctx, groupCounterKey, 0) storage.Put(ctx, namespaceKey(""), std.Serialize(Namespace{})) runtime.Log("frostfsid contract initialized") } // SetAdmin sets the admin address for the contract. func SetAdmin(addr interop.Hash160) { ctx := storage.GetContext() if !common.HasUpdateAccess() { panic("not witnessed") } storage.Put(ctx, adminKey, addr) } // ClearAdmin removes the admin address from the contract storage. func ClearAdmin() { ctx := storage.GetContext() if !common.HasUpdateAccess() { panic("not witnessed") } storage.Delete(ctx, adminKey) } // GetAdmin retrieves the admin address from the contract storage. func GetAdmin() interop.Hash160 { ctx := storage.GetReadOnlyContext() return storage.Get(ctx, adminKey).(interop.Hash160) } // Update method updates contract source code and manifest. It can be invoked // only by committee. func Update(script []byte, manifest []byte, data any) { if !common.HasUpdateAccess() { panic("only committee can update contract") } management.UpdateWithData(script, manifest, common.AppendVersion(data)) runtime.Log("frostfsid contract updated") } // Version returns the version of the contract. func Version() int { return common.Version } // CreateSubject creates a new subject in the specified namespace with the provided public key. func CreateSubject(ns string, key interop.PublicKey) { ctx := storage.GetContext() checkContractOwner(ctx) if len(key) != interop.PublicKeyCompressedLen { panic("incorrect public key length") } 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") } allAddressKey := addressKey(addr) if storage.Get(ctx, allAddressKey) != nil { panic("key is occupied by another additional key") } nsKey := namespaceKey(ns) data = storage.Get(ctx, nsKey).([]byte) if data == nil { panic("namespace not found") } subj := Subject{ PrimaryKey: key, Namespace: ns, } storage.Put(ctx, sKey, std.Serialize(subj)) nsSubjKey := namespaceSubjectKey(ns, addr) storage.Put(ctx, nsSubjKey, []byte{1}) storage.Put(ctx, allAddressKey, true) runtime.Notify("CreateSubject", interop.Hash160(addr)) } // AddSubjectKey adds an additional public key to a subject with the specified address. 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") } addressKey := addressKey(contract.CreateStandardAccount(key)) if storage.Get(ctx, addressKey) != nil { panic("key is occupied") } 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)) storage.Put(ctx, addressKey, true) runtime.Notify("AddSubjectKey", addr, key) } // RemoveSubjectKey removes an additional public key from the subject with the specified address. 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)) storage.Delete(ctx, addressKey(contract.CreateStandardAccount(key))) runtime.Notify("RemoveSubjectKey", addr, key) } // SetSubjectName sets a new name for the subject with the specified address. 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) } // SetSubjectKV sets a key-value pair for the subject with the specified address. 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) } // DeleteSubjectKV deletes a key-value pair from the subject with the specified address. 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) } // DeleteSubject deletes the subject with the specified address. 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, addressKey(contract.CreateStandardAccount(subj.AdditionalKeys[i]))) } storage.Delete(ctx, sKey) removeSubjectFromNamespace(ctx, subj.Namespace, addr) deleteNamespaceSubjectName(ctx, subj.Namespace, subj.Name) runtime.Notify("DeleteSubject", addr) } // GetSubject retrieves the subject with the specified address. 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 { a := getPrimaryAddr(ctx, addr) sKey = subjectKeyFromAddr(a) data = storage.Get(ctx, sKey).([]byte) if data == nil { panic("subject not found") } } return std.Deserialize(data).(Subject) } // GetSubjectExtended retrieves the extended information of the subject with the specified address. func GetSubjectExtended(addr interop.Hash160) SubjectExtended { subj := GetSubject(addr) ctx := storage.GetReadOnlyContext() var groups []Group 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 } // GetSubjectByKey retrieves the subject associated with the provided public key. 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) } addr := getPrimaryAddr(ctx, contract.CreateStandardAccount(key)) sKey = subjectKeyFromAddr(addr) data = storage.Get(ctx, sKey).([]byte) if data != nil { return std.Deserialize(data).(Subject) } panic("subject not found") } func getPrimaryAddr(ctx storage.Context, addr interop.Hash160) interop.Hash160 { saPrefix := append([]byte{additionalKeysPrefix}, addr...) it := storage.Find(ctx, saPrefix, storage.KeysOnly|storage.RemovePrefix) if iterator.Next(it) { return iterator.Value(it).([]byte) } panic("subject not found") } // GetSubjectByName retrieves the subject with the specified name within the given namespace. func GetSubjectByName(ns, name string) Subject { key := GetSubjectKeyByName(ns, name) addr := contract.CreateStandardAccount(key) return GetSubject(addr) } // GetSubjectKeyByName retrieves the public key of the subject with the specified namespace and name. func GetSubjectKeyByName(ns, name string) interop.PublicKey { if name == "" { panic("invalid 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 } // GetSubjectKV GetSubjectKey returns the value associated with the key for the subject. func GetSubjectKV(addr interop.Hash160, name string) string { if len(addr) != interop.Hash160Len { panic("incorrect address length") } ctx := storage.GetReadOnlyContext() sKey := subjectKeyFromAddr(addr) data := storage.Get(ctx, sKey).([]byte) if data == nil { return "" } sbj := std.Deserialize(data).(Subject) if sbj.KV == nil { return "" } for k, v := range sbj.KV { if k == name { return v } } return "" } func ListSubjects() iterator.Iterator { ctx := storage.GetReadOnlyContext() return storage.Find(ctx, []byte{subjectKeysPrefix}, storage.KeysOnly|storage.RemovePrefix) } // CreateNamespace creates a new namespace with the specified name. func CreateNamespace(ns string) { ctx := storage.GetContext() checkContractOwner(ctx) 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) } // GetNamespace retrieves the namespace with the specified name. 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) } // GetNamespaceExtended retrieves extended information about the 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 ListNamespaceSubjects(ns string) iterator.Iterator { ctx := storage.GetReadOnlyContext() return storage.Find(ctx, namespaceSubjectPrefix(ns), storage.KeysOnly|storage.RemovePrefix) } // CreateGroup creates a new group within the specified namespace. func CreateGroup(ns, group string) int { ctx := storage.GetContext() checkContractOwner(ctx) if group == "" { panic("invalid group name") } nsKey := namespaceKey(ns) data := storage.Get(ctx, nsKey).([]byte) if data == nil { panic("namespace not found") } groupCountID := storage.Get(ctx, groupCounterKey).(int) groupCountID++ storage.Put(ctx, groupCounterKey, groupCountID) gr := Group{ ID: groupCountID, Name: group, Namespace: ns, } setNamespaceGroupName(ctx, gr) gKey := groupKey(ns, groupCountID) storage.Put(ctx, gKey, std.Serialize(gr)) runtime.Notify("CreateGroup", ns, group) return groupCountID } // GetGroup retrieves the group with the specified ID within the given namespace. func GetGroup(ns string, groupID int) Group { ctx := storage.GetReadOnlyContext() gKey := groupKey(ns, groupID) data := storage.Get(ctx, gKey).([]byte) if data == nil { panic("group not found") } return std.Deserialize(data).(Group) } // GetGroupExtended retrieves extended information about the group, including the count of subjects in the group. func GetGroupExtended(ns string, groupID int) GroupExtended { ctx := storage.GetReadOnlyContext() gKey := groupKey(ns, groupID) data := storage.Get(ctx, gKey).([]byte) if data == nil { panic("group not found") } gr := std.Deserialize(data).(Group) grExtended := GroupExtended{ ID: gr.ID, Name: gr.Name, Namespace: gr.Namespace, KV: gr.KV, } it := storage.Find(ctx, groupSubjectPrefix(ns, groupID), storage.KeysOnly) for iterator.Next(it) { grExtended.SubjectsCount += 1 } return grExtended } // GetGroupIDByName retrieves the ID of the group with the specified name within the given namespace. func GetGroupIDByName(ns, name string) int { if name == "" { panic("invalid name") } ctx := storage.GetReadOnlyContext() nsGroupNameKey := namespaceGroupNameKey(ns, name) groupIDRaw := storage.Get(ctx, nsGroupNameKey).([]byte) if groupIDRaw == nil { panic("group not found") } return std.Deserialize(groupIDRaw).(int) } // GetGroupByName retrieves the group with the specified name within the given namespace. func GetGroupByName(ns, name string) Group { groupID := GetGroupIDByName(ns, name) gKey := groupKey(ns, groupID) ctx := storage.GetReadOnlyContext() data := storage.Get(ctx, gKey).([]byte) if data == nil { panic("group not found") } return std.Deserialize(data).(Group) } // SetGroupName sets a new name for the group with the specified ID within the given namespace. func SetGroupName(ns string, groupID int, name string) { ctx := storage.GetContext() checkContractOwner(ctx) gKey := groupKey(ns, groupID) data := storage.Get(ctx, gKey).([]byte) if data == nil { panic("group not found") } gr := std.Deserialize(data).(Group) oldName := gr.Name gr.Name = name storage.Put(ctx, gKey, std.Serialize(gr)) updateNamespaceGroupName(ctx, gr, oldName) runtime.Notify("SetGroupName", ns, groupID, name) } // SetGroupKV sets a key-value pair for the group with the specified ID within the given namespace. func SetGroupKV(ns string, groupID int, key, val string) { ctx := storage.GetContext() checkContractOwner(ctx) gKey := groupKey(ns, groupID) data := storage.Get(ctx, gKey).([]byte) if data == nil { panic("group not found") } gr := std.Deserialize(data).(Group) if gr.KV == nil { gr.KV = map[string]string{} } gr.KV[key] = val storage.Put(ctx, gKey, std.Serialize(gr)) runtime.Notify("SetGroupKV", ns, groupID, key, val) } // DeleteGroupKV deletes a key-value pair from the group with the specified ID within the given namespace. func DeleteGroupKV(ns string, groupID int, key string) { ctx := storage.GetContext() checkContractOwner(ctx) gKey := groupKey(ns, groupID) data := storage.Get(ctx, gKey).([]byte) if data == nil { panic("group not found") } gr := std.Deserialize(data).(Group) delete(gr.KV, key) storage.Put(ctx, gKey, std.Serialize(gr)) runtime.Notify("DeleteGroupKV", ns, groupID, key) } func ListGroups(ns string) iterator.Iterator { ctx := storage.GetReadOnlyContext() return storage.Find(ctx, groupPrefix(ns), storage.ValuesOnly|storage.DeserializeValues) } // AddSubjectToGroup adds a subject to a group with the specified ID. func AddSubjectToGroup(addr interop.Hash160, groupID int) { 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) gKey := groupKey(subject.Namespace, groupID) data = storage.Get(ctx, gKey).([]byte) if data == nil { panic("group not found") } group := std.Deserialize(data).(Group) if group.Namespace != subject.Namespace { panic("subject and group must be in the same namespace") } gsKey := groupSubjectKey(subject.Namespace, groupID, addr) storage.Put(ctx, gsKey, []byte{1}) runtime.Notify("AddSubjectToGroup", addr, subject.Namespace, groupID) } // RemoveSubjectFromGroup removes a subject from a group with the specified ID. func RemoveSubjectFromGroup(addr interop.Hash160, groupID int) { 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) gKey := groupKey(subject.Namespace, groupID) data = storage.Get(ctx, gKey).([]byte) if data == nil { panic("group not found") } group := std.Deserialize(data).(Group) if group.Namespace != subject.Namespace { panic("subject and group must be in the same namespace") } gsKey := groupSubjectKey(subject.Namespace, groupID, addr) storage.Delete(ctx, gsKey) runtime.Notify("RemoveSubjectFromGroup", addr, subject.Namespace, groupID) } func ListGroupSubjects(ns string, groupID int) iterator.Iterator { ctx := storage.GetReadOnlyContext() return storage.Find(ctx, groupSubjectPrefix(ns, groupID), storage.KeysOnly|storage.RemovePrefix) } // DeleteGroup deletes the group with the specified ID within the given namespace. func DeleteGroup(ns string, groupID int) { ctx := storage.GetContext() checkContractOwner(ctx) gKey := groupKey(ns, groupID) data := storage.Get(ctx, gKey).([]byte) if data == nil { panic("group not found") } gr := std.Deserialize(data).(Group) storage.Delete(ctx, gKey) it := storage.Find(ctx, groupSubjectPrefix(ns, groupID), storage.KeysOnly) for iterator.Next(it) { gsKey := iterator.Value(it).([]byte) storage.Delete(ctx, gsKey) } deleteNamespaceGroupName(ctx, ns, gr.Name) runtime.Notify("DeleteGroup", ns, groupID) } func checkContractOwner(ctx storage.Context) { addr := storage.Get(ctx, adminKey) if addr != nil && runtime.CheckWitness(addr.(interop.Hash160)) { return } if common.HasUpdateAccess() { 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 updateNamespaceGroupName(ctx storage.Context, gr Group, oldName string) { if gr.Name == oldName { return } deleteNamespaceGroupName(ctx, gr.Namespace, oldName) setNamespaceGroupName(ctx, gr) } func deleteNamespaceGroupName(ctx storage.Context, ns, grName string) { if grName == "" { return } nsGroupNameKey := namespaceGroupNameKey(ns, grName) storage.Delete(ctx, nsGroupNameKey) } func setNamespaceGroupName(ctx storage.Context, gr Group) { if gr.Name == "" { return } nsGroupNameKey := namespaceGroupNameKey(gr.Namespace, gr.Name) groupIDRaw := storage.Get(ctx, nsGroupNameKey).([]byte) if groupIDRaw == nil { storage.Put(ctx, nsGroupNameKey, std.Serialize(gr.ID)) return } groupID := std.Deserialize(groupIDRaw).(int) if groupID != gr.ID { panic("group name is not available in the current namespace") } } 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 string, groupID int) []byte { prefix := groupPrefix(ns) return append(prefix, idToBytes(groupID)...) } func groupKeyFromHashes(nsHash []byte, groupIDBytes []byte) []byte { prefix := groupPrefixFromHash(nsHash) return append(prefix, groupIDBytes...) } 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 namespaceGroupNameKey(ns, groupName string) []byte { nsHash := ripemd160Hash(ns) nameHash := ripemd160Hash(groupName) return append([]byte{namespaceGroupsNamesPrefix}, append(nsHash, nameHash...)...) } func groupSubjectKey(ns string, groupID int, addr interop.Hash160) []byte { prefix := groupSubjectPrefix(ns, groupID) return append(prefix, addr...) } func groupSubjectKeyFromHashes(nsHash, groupHash []byte, addr interop.Hash160) []byte { prefix := groupSubjectPrefixFromHashes(nsHash, groupHash) return append(prefix, addr...) } func groupSubjectPrefix(ns string, groupID int) []byte { nsHash := ripemd160Hash(ns) return groupSubjectPrefixFromHashes(nsHash, idToBytes(groupID)) } func groupSubjectPrefixFromHashes(nsHash, groupIDBytes []byte) []byte { return append([]byte{groupSubjectsKeysPrefix}, append(nsHash, groupIDBytes...)...) } // idToBytes converts i64 value to BE bytes. Panics if value is bigger than i64. func idToBytes(itemID int) []byte { b := convert.ToBytes(itemID) ln := len(b) if ln > 8 { panic("item ID exceeds 8 byte limit") } zeros := make([]byte, 8-ln) return append(b, zeros...) } func addressKey(address []byte) []byte { return append([]byte{addressPrefix}, address...) }