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 ( 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 ( 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() args := data.(struct { owners []interop.Hash160 }) for _, owner := range args.owners { if len(owner) != interop.Hash160Len { panic("incorrect length of owner addresses") } storage.Put(ctx, ownerKey(owner), []byte{1}) } 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) { 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 } func CreateSubject(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") } 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...)...) }