forked from TrueCloudLab/frostfs-api-go
initial
This commit is contained in:
commit
1cf33e5ffd
87 changed files with 29835 additions and 0 deletions
185
chain/address.go
Normal file
185
chain/address.go
Normal file
|
@ -0,0 +1,185 @@
|
|||
package chain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/mr-tron/base58"
|
||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
||||
"github.com/nspcc-dev/neofs-proto/internal"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/ripemd160"
|
||||
)
|
||||
|
||||
// WalletAddress implements NEO address.
|
||||
type WalletAddress [AddressLength]byte
|
||||
|
||||
const (
|
||||
// AddressLength contains size of address,
|
||||
// 0x17 byte (address version) + 20 bytes of ScriptHash + 4 bytes of checksum.
|
||||
AddressLength = 25
|
||||
|
||||
// ScriptHashLength contains size of ScriptHash.
|
||||
ScriptHashLength = 20
|
||||
|
||||
// ErrEmptyAddress is raised when empty Address is passed.
|
||||
ErrEmptyAddress = internal.Error("empty address")
|
||||
|
||||
// ErrAddressLength is raised when passed address has wrong size.
|
||||
ErrAddressLength = internal.Error("wrong address length")
|
||||
)
|
||||
|
||||
func checksum(sign []byte) []byte {
|
||||
hash := sha256.Sum256(sign)
|
||||
hash = sha256.Sum256(hash[:])
|
||||
return hash[:4]
|
||||
}
|
||||
|
||||
// FetchPublicKeys tries to parse public keys from verification script.
|
||||
func FetchPublicKeys(vs []byte) []*ecdsa.PublicKey {
|
||||
var (
|
||||
count int
|
||||
offset int
|
||||
ln = len(vs)
|
||||
result []*ecdsa.PublicKey
|
||||
)
|
||||
|
||||
switch {
|
||||
case ln < 1: // wrong data size
|
||||
return nil
|
||||
case vs[ln-1] == 0xac: // last byte is CHECKSIG
|
||||
count = 1
|
||||
case vs[ln-1] == 0xae: // last byte is CHECKMULTISIG
|
||||
// 2nd byte from the end indicates about PK's count
|
||||
count = int(vs[ln-2] - 0x50)
|
||||
// ignores CHECKMULTISIG
|
||||
offset = 1
|
||||
default: // unknown type
|
||||
return nil
|
||||
}
|
||||
|
||||
result = make([]*ecdsa.PublicKey, 0, count)
|
||||
for i := 0; i < count; i++ {
|
||||
// ignores PUSHBYTE33 and tries to parse
|
||||
from, to := offset+1, offset+1+crypto.PublicKeyCompressedSize
|
||||
|
||||
// when passed VerificationScript has wrong size
|
||||
if len(vs) < to {
|
||||
return nil
|
||||
}
|
||||
|
||||
key := crypto.UnmarshalPublicKey(vs[from:to])
|
||||
// when wrong public key is passed
|
||||
if key == nil {
|
||||
return nil
|
||||
}
|
||||
result = append(result, key)
|
||||
|
||||
offset += 1 + crypto.PublicKeyCompressedSize
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// VerificationScript returns VerificationScript composed from public keys.
|
||||
func VerificationScript(pubs ...*ecdsa.PublicKey) []byte {
|
||||
var (
|
||||
pre []byte
|
||||
suf []byte
|
||||
body []byte
|
||||
offset int
|
||||
lnPK = len(pubs)
|
||||
ln = crypto.PublicKeyCompressedSize*lnPK + lnPK // 33 * count + count * 1 (PUSHBYTES33)
|
||||
)
|
||||
|
||||
if len(pubs) > 1 {
|
||||
pre = []byte{0x51} // one address
|
||||
suf = []byte{byte(0x50 + lnPK), 0xae} // count of PK's + CHECKMULTISIG
|
||||
} else {
|
||||
suf = []byte{0xac} // CHECKSIG
|
||||
}
|
||||
|
||||
ln += len(pre) + len(suf)
|
||||
|
||||
body = make([]byte, ln)
|
||||
offset += copy(body, pre)
|
||||
|
||||
for i := range pubs {
|
||||
body[offset] = 0x21
|
||||
offset++
|
||||
offset += copy(body[offset:], crypto.MarshalPublicKey(pubs[i]))
|
||||
}
|
||||
|
||||
copy(body[offset:], suf)
|
||||
|
||||
return body
|
||||
}
|
||||
|
||||
// KeysToAddress return NEO address composed from public keys.
|
||||
func KeysToAddress(pubs ...*ecdsa.PublicKey) string {
|
||||
if len(pubs) == 0 {
|
||||
return ""
|
||||
}
|
||||
return Address(VerificationScript(pubs...))
|
||||
}
|
||||
|
||||
// Address returns NEO address based on passed VerificationScript.
|
||||
func Address(verificationScript []byte) string {
|
||||
sign := [AddressLength]byte{0x17}
|
||||
hash := sha256.Sum256(verificationScript)
|
||||
ripe := ripemd160.New()
|
||||
ripe.Write(hash[:])
|
||||
copy(sign[1:], ripe.Sum(nil))
|
||||
copy(sign[21:], checksum(sign[:21]))
|
||||
return base58.Encode(sign[:])
|
||||
}
|
||||
|
||||
// ReversedScriptHashToAddress parses script hash and returns valid NEO address.
|
||||
func ReversedScriptHashToAddress(sc string) (addr string, err error) {
|
||||
var data []byte
|
||||
if data, err = DecodeScriptHash(sc); err != nil {
|
||||
return
|
||||
}
|
||||
sign := [AddressLength]byte{0x17}
|
||||
copy(sign[1:], data)
|
||||
copy(sign[1+ScriptHashLength:], checksum(sign[:1+ScriptHashLength]))
|
||||
return base58.Encode(sign[:]), nil
|
||||
}
|
||||
|
||||
// IsAddress checks that passed NEO Address is valid.
|
||||
func IsAddress(s string) error {
|
||||
if s == "" {
|
||||
return ErrEmptyAddress
|
||||
} else if addr, err := base58.Decode(s); err != nil {
|
||||
return errors.Wrap(err, "base58 decode")
|
||||
} else if ln := len(addr); ln != AddressLength {
|
||||
return errors.Wrapf(ErrAddressLength, "length %d != %d", AddressLength, ln)
|
||||
} else if sum := checksum(addr[:21]); !bytes.Equal(addr[21:], sum) {
|
||||
return errors.Errorf("wrong checksum %0x != %0x",
|
||||
addr[21:], sum)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReverseBytes returns reversed []byte of given.
|
||||
func ReverseBytes(data []byte) []byte {
|
||||
for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 {
|
||||
data[i], data[j] = data[j], data[i]
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// DecodeScriptHash parses script hash into slice of bytes.
|
||||
func DecodeScriptHash(s string) ([]byte, error) {
|
||||
if s == "" {
|
||||
return nil, ErrEmptyAddress
|
||||
} else if addr, err := hex.DecodeString(s); err != nil {
|
||||
return nil, errors.Wrap(err, "hex decode")
|
||||
} else if ln := len(addr); ln != ScriptHashLength {
|
||||
return nil, errors.Wrapf(ErrAddressLength, "length %d != %d", ScriptHashLength, ln)
|
||||
} else {
|
||||
return addr, nil
|
||||
}
|
||||
}
|
292
chain/address_test.go
Normal file
292
chain/address_test.go
Normal file
|
@ -0,0 +1,292 @@
|
|||
package chain
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
crypto "github.com/nspcc-dev/neofs-crypto"
|
||||
"github.com/nspcc-dev/neofs-crypto/test"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAddress(t *testing.T) {
|
||||
var (
|
||||
multiSigVerificationScript = "512103c02a93134f98d9c78ec54b1b1f97fc64cd81360f53a293f41e4ad54aac3c57172103fea219d4ccfd7641cebbb2439740bb4bd7c4730c1abd6ca1dc44386533816df952ae"
|
||||
multiSigAddress = "ANbvKqa2SfgTUkq43NRUhCiyxPrpUPn7S3"
|
||||
|
||||
normalVerificationScript = "2102a33413277a319cc6fd4c54a2feb9032eba668ec587f307e319dc48733087fa61ac"
|
||||
normalAddress = "AcraNnCuPKnUYtPYyrACRCVJhLpvskbfhu"
|
||||
)
|
||||
|
||||
t.Run("check multi-sig address", func(t *testing.T) {
|
||||
data, err := hex.DecodeString(multiSigVerificationScript)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, multiSigAddress, Address(data))
|
||||
})
|
||||
|
||||
t.Run("check normal address", func(t *testing.T) {
|
||||
data, err := hex.DecodeString(normalVerificationScript)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, normalAddress, Address(data))
|
||||
})
|
||||
}
|
||||
|
||||
func TestVerificationScript(t *testing.T) {
|
||||
t.Run("check normal", func(t *testing.T) {
|
||||
pkString := "02a33413277a319cc6fd4c54a2feb9032eba668ec587f307e319dc48733087fa61"
|
||||
|
||||
pkBytes, err := hex.DecodeString(pkString)
|
||||
require.NoError(t, err)
|
||||
|
||||
pk := crypto.UnmarshalPublicKey(pkBytes)
|
||||
|
||||
expect, err := hex.DecodeString(
|
||||
"21" + pkString + // PUSHBYTES33
|
||||
"ac", // CHECKSIG
|
||||
)
|
||||
|
||||
require.Equal(t, expect, VerificationScript(pk))
|
||||
})
|
||||
|
||||
t.Run("check multisig", func(t *testing.T) {
|
||||
pk1String := "03c02a93134f98d9c78ec54b1b1f97fc64cd81360f53a293f41e4ad54aac3c5717"
|
||||
pk2String := "03fea219d4ccfd7641cebbb2439740bb4bd7c4730c1abd6ca1dc44386533816df9"
|
||||
|
||||
pk1Bytes, err := hex.DecodeString(pk1String)
|
||||
require.NoError(t, err)
|
||||
|
||||
pk1 := crypto.UnmarshalPublicKey(pk1Bytes)
|
||||
|
||||
pk2Bytes, err := hex.DecodeString(pk2String)
|
||||
require.NoError(t, err)
|
||||
|
||||
pk2 := crypto.UnmarshalPublicKey(pk2Bytes)
|
||||
|
||||
expect, err := hex.DecodeString(
|
||||
"51" + // one address
|
||||
"21" + pk1String + // PUSHBYTES33
|
||||
"21" + pk2String + // PUSHBYTES33
|
||||
"52" + // 2 PublicKeys
|
||||
"ae", // CHECKMULTISIG
|
||||
)
|
||||
|
||||
require.Equal(t, expect, VerificationScript(pk1, pk2))
|
||||
})
|
||||
}
|
||||
|
||||
func TestKeysToAddress(t *testing.T) {
|
||||
t.Run("check normal", func(t *testing.T) {
|
||||
pkString := "02a33413277a319cc6fd4c54a2feb9032eba668ec587f307e319dc48733087fa61"
|
||||
|
||||
pkBytes, err := hex.DecodeString(pkString)
|
||||
require.NoError(t, err)
|
||||
|
||||
pk := crypto.UnmarshalPublicKey(pkBytes)
|
||||
|
||||
expect := "AcraNnCuPKnUYtPYyrACRCVJhLpvskbfhu"
|
||||
|
||||
actual := KeysToAddress(pk)
|
||||
require.Equal(t, expect, actual)
|
||||
require.NoError(t, IsAddress(actual))
|
||||
})
|
||||
|
||||
t.Run("check multisig", func(t *testing.T) {
|
||||
pk1String := "03c02a93134f98d9c78ec54b1b1f97fc64cd81360f53a293f41e4ad54aac3c5717"
|
||||
pk2String := "03fea219d4ccfd7641cebbb2439740bb4bd7c4730c1abd6ca1dc44386533816df9"
|
||||
|
||||
pk1Bytes, err := hex.DecodeString(pk1String)
|
||||
require.NoError(t, err)
|
||||
|
||||
pk1 := crypto.UnmarshalPublicKey(pk1Bytes)
|
||||
|
||||
pk2Bytes, err := hex.DecodeString(pk2String)
|
||||
require.NoError(t, err)
|
||||
|
||||
pk2 := crypto.UnmarshalPublicKey(pk2Bytes)
|
||||
|
||||
expect := "ANbvKqa2SfgTUkq43NRUhCiyxPrpUPn7S3"
|
||||
actual := KeysToAddress(pk1, pk2)
|
||||
require.Equal(t, expect, actual)
|
||||
require.NoError(t, IsAddress(actual))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFetchPublicKeys(t *testing.T) {
|
||||
var (
|
||||
multiSigVerificationScript = "512103c02a93134f98d9c78ec54b1b1f97fc64cd81360f53a293f41e4ad54aac3c57172103fea219d4ccfd7641cebbb2439740bb4bd7c4730c1abd6ca1dc44386533816df952ae"
|
||||
normalVerificationScript = "2102a33413277a319cc6fd4c54a2feb9032eba668ec587f307e319dc48733087fa61ac"
|
||||
|
||||
pk1String = "03c02a93134f98d9c78ec54b1b1f97fc64cd81360f53a293f41e4ad54aac3c5717"
|
||||
pk2String = "03fea219d4ccfd7641cebbb2439740bb4bd7c4730c1abd6ca1dc44386533816df9"
|
||||
pk3String = "02a33413277a319cc6fd4c54a2feb9032eba668ec587f307e319dc48733087fa61"
|
||||
)
|
||||
|
||||
t.Run("shouls not fail", func(t *testing.T) {
|
||||
wrongVS, err := hex.DecodeString(multiSigVerificationScript)
|
||||
require.NoError(t, err)
|
||||
|
||||
wrongVS[len(wrongVS)-1] = 0x1
|
||||
|
||||
wrongPK, err := hex.DecodeString(multiSigVerificationScript)
|
||||
require.NoError(t, err)
|
||||
wrongPK[2] = 0x1
|
||||
|
||||
var testCases = []struct {
|
||||
name string
|
||||
value []byte
|
||||
}{
|
||||
{name: "empty VerificationScript"},
|
||||
{
|
||||
name: "wrong size VerificationScript",
|
||||
value: []byte{0x1},
|
||||
},
|
||||
{
|
||||
name: "wrong VerificationScript type",
|
||||
value: wrongVS,
|
||||
},
|
||||
{
|
||||
name: "wrong public key in VerificationScript",
|
||||
value: wrongPK,
|
||||
},
|
||||
}
|
||||
|
||||
for i := range testCases {
|
||||
tt := testCases[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var keys []*ecdsa.PublicKey
|
||||
require.NotPanics(t, func() {
|
||||
keys = FetchPublicKeys(tt.value)
|
||||
})
|
||||
require.Nil(t, keys)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("check multi-sig address", func(t *testing.T) {
|
||||
data, err := hex.DecodeString(multiSigVerificationScript)
|
||||
require.NoError(t, err)
|
||||
|
||||
pk1Bytes, err := hex.DecodeString(pk1String)
|
||||
require.NoError(t, err)
|
||||
|
||||
pk2Bytes, err := hex.DecodeString(pk2String)
|
||||
require.NoError(t, err)
|
||||
|
||||
pk1 := crypto.UnmarshalPublicKey(pk1Bytes)
|
||||
pk2 := crypto.UnmarshalPublicKey(pk2Bytes)
|
||||
|
||||
keys := FetchPublicKeys(data)
|
||||
require.Len(t, keys, 2)
|
||||
require.Equal(t, keys[0], pk1)
|
||||
require.Equal(t, keys[1], pk2)
|
||||
})
|
||||
|
||||
t.Run("check normal address", func(t *testing.T) {
|
||||
data, err := hex.DecodeString(normalVerificationScript)
|
||||
require.NoError(t, err)
|
||||
|
||||
pkBytes, err := hex.DecodeString(pk3String)
|
||||
require.NoError(t, err)
|
||||
|
||||
pk := crypto.UnmarshalPublicKey(pkBytes)
|
||||
|
||||
keys := FetchPublicKeys(data)
|
||||
require.Len(t, keys, 1)
|
||||
require.Equal(t, keys[0], pk)
|
||||
})
|
||||
|
||||
t.Run("generate 10 keys VerificationScript and try parse it", func(t *testing.T) {
|
||||
var (
|
||||
count = 10
|
||||
expect = make([]*ecdsa.PublicKey, 0, count)
|
||||
)
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
key := test.DecodeKey(i)
|
||||
expect = append(expect, &key.PublicKey)
|
||||
}
|
||||
|
||||
vs := VerificationScript(expect...)
|
||||
|
||||
actual := FetchPublicKeys(vs)
|
||||
require.Equal(t, expect, actual)
|
||||
})
|
||||
}
|
||||
|
||||
func TestReversedScriptHashToAddress(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
name string
|
||||
value string
|
||||
expect string
|
||||
}{
|
||||
{
|
||||
name: "first",
|
||||
expect: "APfiG5imQgn8dzTTfaDfqHnxo3QDUkF69A",
|
||||
value: "5696acd07f0927fd5f01946828638c9e2c90c5dc",
|
||||
},
|
||||
|
||||
{
|
||||
name: "second",
|
||||
expect: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
|
||||
value: "23ba2703c53263e8d6e522dc32203339dcd8eee9",
|
||||
},
|
||||
}
|
||||
|
||||
for i := range testCases {
|
||||
tt := testCases[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual, err := ReversedScriptHashToAddress(tt.value)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.expect, actual)
|
||||
require.NoError(t, IsAddress(actual))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReverseBytes(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
name string
|
||||
value []byte
|
||||
expect []byte
|
||||
}{
|
||||
{name: "empty"},
|
||||
{
|
||||
name: "single byte",
|
||||
expect: []byte{0x1},
|
||||
value: []byte{0x1},
|
||||
},
|
||||
|
||||
{
|
||||
name: "two bytes",
|
||||
expect: []byte{0x2, 0x1},
|
||||
value: []byte{0x1, 0x2},
|
||||
},
|
||||
|
||||
{
|
||||
name: "three bytes",
|
||||
expect: []byte{0x3, 0x2, 0x1},
|
||||
value: []byte{0x1, 0x2, 0x3},
|
||||
},
|
||||
|
||||
{
|
||||
name: "five bytes",
|
||||
expect: []byte{0x5, 0x4, 0x3, 0x2, 0x1},
|
||||
value: []byte{0x1, 0x2, 0x3, 0x4, 0x5},
|
||||
},
|
||||
|
||||
{
|
||||
name: "eight bytes",
|
||||
expect: []byte{0x8, 0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1},
|
||||
value: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8},
|
||||
},
|
||||
}
|
||||
|
||||
for i := range testCases {
|
||||
tt := testCases[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual := ReverseBytes(tt.value)
|
||||
require.Equal(t, tt.expect, actual)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue