diff --git a/frostfsid/doc.go b/frostfsid/doc.go index 9e02ce4..b3d5b82 100644 --- a/frostfsid/doc.go +++ b/frostfsid/doc.go @@ -20,6 +20,7 @@ FrostFSID contract does not produce notifications to process. | `G` + [ RIPEMD160 of namespace ] + [ 8 byte group id ] + [ subject address ] | []byte{1} | subject that belongs to the group | | `c` | Int | group id counter | | `m` + [ RIPEMD160 of namespace ] + [ RIPEMD160 of subject name ] | Serialized group id int | group name to group id index | + | `A` + [ subject address ] | bool | means that the wallet has been used | */ diff --git a/frostfsid/frostfsid_contract.go b/frostfsid/frostfsid_contract.go index a9cca0e..fd2d446 100644 --- a/frostfsid/frostfsid_contract.go +++ b/frostfsid/frostfsid_contract.go @@ -96,6 +96,7 @@ const ( groupSubjectsKeysPrefix = 'G' groupCounterKey = 'c' namespaceGroupsNamesPrefix = 'm' + addressPrefix = 'A' ) func _deploy(data any, isUpdate bool) { @@ -112,6 +113,27 @@ func _deploy(data any, isUpdate bool) { storage.Put(ctx, adminKey, args.admin) } + if isUpdate { + 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{})) @@ -182,6 +204,11 @@ func CreateSubject(ns string, key interop.PublicKey) { 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 { @@ -197,6 +224,7 @@ func CreateSubject(ns string, key interop.PublicKey) { nsSubjKey := namespaceSubjectKey(ns, addr) storage.Put(ctx, nsSubjKey, []byte{1}) + storage.Put(ctx, allAddressKey, true) runtime.Notify("CreateSubject", interop.Hash160(addr)) } @@ -213,6 +241,11 @@ func AddSubjectKey(addr interop.Hash160, key interop.PublicKey) { 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 { @@ -230,6 +263,7 @@ func AddSubjectKey(addr interop.Hash160, key interop.PublicKey) { subject.AdditionalKeys = append(subject.AdditionalKeys, key) storage.Put(ctx, sKey, std.Serialize(subject)) + storage.Put(ctx, addressKey, true) runtime.Notify("AddSubjectKey", addr, key) } @@ -269,6 +303,7 @@ func RemoveSubjectKey(addr interop.Hash160, key interop.PublicKey) { subject.AdditionalKeys = additionalKeys storage.Put(ctx, sKey, std.Serialize(subject)) + storage.Delete(ctx, addressKey(contract.CreateStandardAccount(key))) runtime.Notify("RemoveSubjectKey", addr, key) } @@ -362,6 +397,7 @@ func DeleteSubject(addr interop.Hash160) { 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) @@ -1029,3 +1065,7 @@ func idToBytes(itemID int) []byte { zeros := make([]byte, 8-ln) return append(b, zeros...) } + +func addressKey(address []byte) []byte { + return append([]byte{addressPrefix}, address...) +} diff --git a/tests/frostfsid_test.go b/tests/frostfsid_test.go index 0e5a38e..419fb9f 100644 --- a/tests/frostfsid_test.go +++ b/tests/frostfsid_test.go @@ -604,6 +604,44 @@ func TestFrostFSID_GroupManagement(t *testing.T) { }) } +func TestAdditionalKeyFromPrimarySubject(t *testing.T) { + f := newFrostFSIDInvoker(t) + invoker := f.OwnerInvoker() + + subjAPrimaryKey, err := keys.NewPrivateKey() + require.NoError(t, err) + subjAKeyAddr := subjAPrimaryKey.PublicKey().GetScriptHash() + + subjBPrimaryKey, err := keys.NewPrivateKey() + require.NoError(t, err) + subjBKeyAddr := subjBPrimaryKey.PublicKey().GetScriptHash() + + subjCPrimaryKey, err := keys.NewPrivateKey() + require.NoError(t, err) + + subjDPrimaryKey, err := keys.NewPrivateKey() + require.NoError(t, err) + + invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjAPrimaryKey.PublicKey().Bytes()) + + invoker.Invoke(t, stackitem.Null{}, createSubjectMethod, defaultNamespace, subjBPrimaryKey.PublicKey().Bytes()) + + invoker.InvokeFail(t, "key is occupied", addSubjectKeyMethod, subjBKeyAddr, subjAPrimaryKey.PublicKey().Bytes()) + + invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjAKeyAddr, subjCPrimaryKey.PublicKey().Bytes()) + invoker.InvokeFail(t, "key is occupied", addSubjectKeyMethod, subjBKeyAddr, subjCPrimaryKey.PublicKey().Bytes()) + + invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjAKeyAddr, subjDPrimaryKey.PublicKey().Bytes()) + + invoker.InvokeFail(t, "key is occupied", addSubjectKeyMethod, subjBKeyAddr, subjDPrimaryKey.PublicKey().Bytes()) + invoker.Invoke(t, stackitem.Null{}, removeSubjectKeyMethod, subjAKeyAddr, subjDPrimaryKey.PublicKey().Bytes()) + invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjBKeyAddr, subjDPrimaryKey.PublicKey().Bytes()) + + invoker.InvokeFail(t, "key is occupied", addSubjectKeyMethod, subjAKeyAddr, subjDPrimaryKey.PublicKey().Bytes()) + invoker.Invoke(t, stackitem.Null{}, deleteSubjectMethod, subjBKeyAddr) + invoker.Invoke(t, stackitem.Null{}, addSubjectKeyMethod, subjAKeyAddr, subjDPrimaryKey.PublicKey().Bytes()) +} + func checkPublicKeyResult(t *testing.T, s *vm.Stack, err error, key *keys.PrivateKey) { if key == nil { require.ErrorContains(t, err, "not found")