[#48] frostfsid: Update contract

Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
pull/40/head
Denis Kirillov 2023-11-07 13:54:48 +03:00
parent 0ea65ca637
commit edfab37677
3 changed files with 909 additions and 125 deletions

View File

@ -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

View File

@ -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

View File

@ -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...)...)
}