Skip to content

Commit

Permalink
*: Provide constructors for ID types
Browse files Browse the repository at this point in the history
Continues d35587d. Additionally, provide
decode helpers creating new instances.

Refs #483.

Signed-off-by: Leonard Lyubich <leonard@morphbits.io>
  • Loading branch information
cthulhu-rider committed Jul 18, 2024
1 parent 8b416e5 commit 24cf3f8
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 25 deletions.
3 changes: 2 additions & 1 deletion container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -538,8 +538,9 @@ func (x Container) VerifySignature(sig neofscrypto.Signature) bool {
// CalculateID encodes the given Container and passes the result into FromBinary.
//
// See also Container.Marshal, AssertID.
// Deprecated: use cid.NewFromMarshalledContainer(x.Marshal()) instead.
func (x Container) CalculateID(dst *cid.ID) {
dst.FromBinary(x.Marshal())
*dst = cid.NewFromMarshalledContainer(x.Marshal())
}

// AssertID checks if the given Container matches its identifier in CAS of the
Expand Down
30 changes: 23 additions & 7 deletions container/id/id.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,26 @@ const Size = sha256.Size
//
// ID is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.ContainerID
// message. See ReadFromV2 / WriteToV2 methods.
//
// Instances can be created using built-in var declaration.
type ID [Size]byte

// NewFromMarshalledContainer returns new ID calculated from the given NeoFS
// container encoded into Protocol Buffers V3 with ascending order of fields by
// number. It's callers responsibility to ensure the format of b. See
// [container.Container.Marshal].
func NewFromMarshalledContainer(b []byte) ID { return sha256.Sum256(b) }

// DecodeBytes creates new ID and makes [ID.Decode].
func DecodeBytes(b []byte) (ID, error) {
var id ID
return id, id.Decode(b)
}

// DecodeString creates new ID and makes [ID.DecodeString].
func DecodeString(s string) (ID, error) {
var id ID
return id, id.DecodeString(s)
}

// ReadFromV2 reads ID from the refs.ContainerID message.
// Returns an error if the message is malformed according
// to the NeoFS API V2 protocol.
Expand Down Expand Up @@ -53,7 +69,8 @@ func (id ID) Encode(dst []byte) {
copy(dst, id[:])
}

// Decode decodes src bytes into ID.
// Decode decodes src bytes into ID. Use [DecodeBytes] to decode src into a new
// ID.
//
// Decode expects that src has [Size] bytes length. If the input is malformed,
// Decode returns an error describing format violation. In this case ID
Expand Down Expand Up @@ -95,7 +112,7 @@ func (id ID) EncodeToString() string {
}

// DecodeString decodes string into ID according to NeoFS API protocol. Returns
// an error if s is malformed.
// an error if s is malformed. Use [DecodeString] to decode s into a new ID.
//
// See also DecodeString.
func (id *ID) DecodeString(s string) error {
Expand All @@ -120,6 +137,5 @@ func (id ID) String() string {
// in CAS of the NeoFS containers and writes it into id.
//
// See also [container.Container.CalculateID], [container.Container.AssertID].
func (id *ID) FromBinary(cnr []byte) {
*id = sha256.Sum256(cnr)
}
// Deprecated: use [NewFromContainerBinary].
func (id *ID) FromBinary(cnr []byte) { *id = NewFromMarshalledContainer(cnr) }
16 changes: 12 additions & 4 deletions object/id/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,21 @@ import (
//
// Address is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.Address
// message. See ReadFromV2 / WriteToV2 methods.
//
// Instances can be created using built-in var declaration.
type Address struct {
cnr cid.ID

obj ID
}

// NewAddress constructs new Address.
func NewAddress(cnr cid.ID, obj ID) Address { return Address{cnr, obj} }

// DecodeAddressString creates new Address and makes [Address.DecodeString].
func DecodeAddressString(s string) (Address, error) {
var id Address
return id, id.DecodeString(s)
}

// ReadFromV2 reads Address from the refs.Address message. Returns an error if
// the message is malformed according to the NeoFS API V2 protocol.
//
Expand Down Expand Up @@ -140,8 +147,9 @@ func (x Address) EncodeToString() string {
return x.cnr.EncodeToString() + "/" + x.obj.EncodeToString()
}

// DecodeString decodes string into Address according to NeoFS API protocol. Returns
// an error if s is malformed.
// DecodeString decodes string into Address according to NeoFS API protocol.
// Returns an error if s is malformed. Use [DecodeAddressString] to decode s
// into a new ID.
//
// See also DecodeString.
func (x *Address) DecodeString(s string) error {
Expand Down
24 changes: 20 additions & 4 deletions object/id/id.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,25 @@ const Size = sha256.Size
//
// ID is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.ObjectID
// message. See ReadFromV2 / WriteToV2 methods.
//
// Instances can be created using built-in var declaration.
type ID [Size]byte

// NewFromObjectHeaderBinary returns new ID calculated from the given NeoFS
// object header encoded into Protocol Buffers V3 with ascending order of fields
// by number. It's callers responsibility to ensure the format of b.
func NewFromObjectHeaderBinary(b []byte) ID { return sha256.Sum256(b) }

// DecodeBytes creates new ID and makes [ID.Decode].
func DecodeBytes(b []byte) (ID, error) {
var id ID
return id, id.Decode(b)
}

// DecodeString creates new ID and makes [ID.DecodeString].
func DecodeString(s string) (ID, error) {
var id ID
return id, id.DecodeString(s)
}

// ReadFromV2 reads ID from the refs.ObjectID message. Returns an error if
// the message is malformed according to the NeoFS API V2 protocol.
//
Expand Down Expand Up @@ -53,7 +68,8 @@ func (id ID) Encode(dst []byte) {
copy(dst, id[:])
}

// Decode decodes src bytes into ID.
// Decode decodes src bytes into ID. Use [DecodeBytes] to decode src into a new
// ID.
//
// Decode expects that src has [IDSize] bytes length. If the input is malformed,
// Decode returns an error describing format violation. In this case ID
Expand Down Expand Up @@ -95,7 +111,7 @@ func (id ID) EncodeToString() string {
}

// DecodeString decodes string into ID according to NeoFS API protocol. Returns
// an error if s is malformed.
// an error if s is malformed. Use [DecodeString] to decode s into a new ID.
//
// See also DecodeString.
func (id *ID) DecodeString(s string) error {
Expand Down
36 changes: 28 additions & 8 deletions user/id.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package user

import (
"bytes"
"crypto/ecdsa"
"errors"
"fmt"

"github.com/mr-tron/base58"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neofs-api-go/v2/refs"
Expand All @@ -22,10 +24,31 @@ const IDSize = 25
// ID is mutually compatible with github.com/nspcc-dev/neofs-api-go/v2/refs.OwnerID
// message. See ReadFromV2 / WriteToV2 methods.
//
// Instances can be created using built-in var declaration. Zero ID is not valid,
// so it MUST be initialized using some modifying function (e.g. SetScriptHash, etc.).
// Zero ID is not valid.
type ID [IDSize]byte

// NewFromScriptHash creates new ID and makes [ID.SetScriptHash].
func NewFromScriptHash(scriptHash util.Uint160) ID {
var x ID
x[0] = address.Prefix
copy(x[1:], scriptHash.BytesBE())
copy(x[21:], hash.Checksum(x[:21]))
return x
}

// NewFromECDSAPublicKey creates new ID corresponding to Neo3 verification
// script hash of the given ECDSA public key. The point must be on the
// [elliptic.P256] curve.
func NewFromECDSAPublicKey(pub ecdsa.PublicKey) ID {
return NewFromScriptHash((*keys.PublicKey)(&pub).GetScriptHash())
}

// DecodeString creates new ID and makes [ID.DecodeString].
func DecodeString(s string) (ID, error) {
var id ID
return id, id.DecodeString(s)
}

func (x *ID) decodeBytes(b []byte) error {
switch {
case len(b) != IDSize:
Expand Down Expand Up @@ -56,11 +79,8 @@ func (x ID) WriteToV2(m *refs.OwnerID) {
}

// SetScriptHash forms user ID from wallet address scripthash.
func (x *ID) SetScriptHash(scriptHash util.Uint160) {
x[0] = address.Prefix
copy(x[1:], scriptHash.BytesBE())
copy(x[21:], hash.Checksum(x[:21]))
}
// Deprecated: use [NewFromScriptHash] instead.
func (x *ID) SetScriptHash(scriptHash util.Uint160) { *x = NewFromScriptHash(scriptHash) }

// WalletBytes returns NeoFS user ID as Neo3 wallet address in a binary format.
//
Expand All @@ -81,7 +101,7 @@ func (x ID) EncodeToString() string {
}

// DecodeString decodes NeoFS API V2 protocol string. Returns an error
// if s is malformed.
// if s is malformed. Use [DecodeString] to decode s into a new ID.
//
// DecodeString always changes the ID.
//
Expand Down
59 changes: 59 additions & 0 deletions user/id_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package user_test

import (
"bytes"
"crypto/ecdsa"
"crypto/elliptic"
"math/big"
"math/rand"
"testing"

Expand Down Expand Up @@ -119,10 +122,13 @@ func TestID_DecodeString(t *testing.T) {
} {
t.Run(tc.name, func(t *testing.T) {
err := new(user.ID).DecodeString(tc.str)
_, err2 := user.DecodeString(tc.str)
if tc.contains {
require.ErrorContains(t, err, tc.err, tc)
require.ErrorContains(t, err2, tc.err, tc)
} else {
require.EqualError(t, err, tc.err, tc)
require.EqualError(t, err2, tc.err, tc)
}
})
}
Expand All @@ -149,3 +155,56 @@ func TestIDComparable(t *testing.T) {
require.False(t, x == y)
require.True(t, x != y)
}

func TestNewFromScriptHash(t *testing.T) {
scriptHash := util.Uint160{11, 169, 152, 95, 44, 8, 18, 164, 109, 197, 177, 25, 236, 41, 179, 46, 235, 84, 113, 97}
id := user.NewFromScriptHash(scriptHash)
require.EqualValues(t, 0x35, id[0])
require.Equal(t, scriptHash[:], id[1:21])
require.Equal(t, []byte{78, 31, 235, 139}, id[21:])
}

func TestNewFromECDSAPublicKey(t *testing.T) {
x := []byte{41, 129, 156, 121, 170, 4, 67, 132, 75, 159, 26, 118, 120, 134, 213, 180, 46, 250, 210, 31, 218, 99, 126, 71, 153, 132, 123, 219, 142, 18, 121, 135}
y := []byte{128, 105, 166, 6, 88, 228, 216, 235, 151, 24, 251, 57, 219, 196, 207, 189, 209, 250, 68, 113, 26, 197, 77, 31, 193, 247, 157, 253, 162, 127, 59, 43}
expected := [user.IDSize]byte{53, 51, 5, 166, 111, 29, 20, 101, 192, 165, 28, 167, 57, 160, 82, 80, 41, 203, 20, 254, 30, 138, 195, 17, 92}
pub := ecdsa.PublicKey{
Curve: elliptic.P256(),
X: new(big.Int).SetBytes(x),
Y: new(big.Int).SetBytes(y),
}
id := user.NewFromECDSAPublicKey(pub)
require.EqualValues(t, expected, id)
}

func TestDecodeString(t *testing.T) {
const s = "NfPCfAR4inFKGZqrjnpMX6yQ5hZCtVpXUx"
b := [user.IDSize]byte{53, 213, 144, 221, 254, 189, 129, 167, 41, 216, 106, 91, 19, 100, 248, 81, 99, 172, 115, 203, 120, 154, 192, 43, 69}
id, err := user.DecodeString(s)
require.NoError(t, err)
require.EqualValues(t, b, id)

t.Run("invalid", func(t *testing.T) {
for _, tc := range []struct {
name string
str string
contains bool
err string
}{
{name: "base58", str: "NfPCfAR4inFKGZqrjnpMX6yQ5hZCtVpXU_", contains: true, err: "decode base58"},
{name: "undersize", str: "5qatkFMhsW6ff9SG6ZYFy1qr6nFN2NBvM", err: "invalid length 24, expected 25"},
{name: "oversize", str: "2er5Dy738K5sXP1QgDNrP3GwRJRKqgizR4Nc", err: "invalid length 26, expected 25"},
{name: "prefix", str: "TtsRYkmYiw6aVsQCw4HR6wKheRqSJLFsLQ", err: "invalid prefix byte 0x42, expected 0x35"},
{name: "checksum", str: "NXsxvBYg6bbDFe2WmitnJ9eZPxDCKmwWVJ", err: "checksum mismatch"},
} {
t.Run(tc.name, func(t *testing.T) {
_, err := user.DecodeString(tc.str)
if tc.contains {
require.ErrorContains(t, err, tc.err, tc)
} else {
require.EqualError(t, err, tc.err, tc)
}
})
}
})
}
1 change: 1 addition & 0 deletions user/signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func NewAutoIDSignerRFC6979(key ecdsa.PrivateKey) Signer {
}

// ResolveFromECDSAPublicKey resolves [ID] from the given [ecdsa.PublicKey].
// Deprecated: use [NewFromECDSAPublicKey].
func ResolveFromECDSAPublicKey(pk ecdsa.PublicKey) ID {
var id ID
id.SetScriptHash((*keys.PublicKey)(&pk).GetScriptHash())
Expand Down
2 changes: 1 addition & 1 deletion user/test/id_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestUser(t *testing.T) {
require.NotEqual(t, s, usertest.User())

require.Equal(t, s.ID, s.UserID())
require.Equal(t, s.ID, user.ResolveFromECDSAPublicKey(s.ECDSAPrivateKey.PublicKey))
require.Equal(t, s.ID, user.NewFromECDSAPublicKey(s.ECDSAPrivateKey.PublicKey))

var pub neofsecdsa.PublicKey
require.NoError(t, pub.Decode(s.PublicKeyBytes))
Expand Down

0 comments on commit 24cf3f8

Please sign in to comment.