github.com/Unheilbar/quorum@v1.0.0/common/types.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package common
    18  
    19  import (
    20  	"bytes"
    21  	"database/sql/driver"
    22  	"encoding/base64"
    23  	"encoding/hex"
    24  	"encoding/json"
    25  	"errors"
    26  	"fmt"
    27  	"io"
    28  	"math/big"
    29  	"math/rand"
    30  	"reflect"
    31  	"strings"
    32  
    33  	"github.com/ethereum/go-ethereum/common/hexutil"
    34  	"github.com/ethereum/go-ethereum/rlp"
    35  	"golang.org/x/crypto/sha3"
    36  )
    37  
    38  // Lengths of hashes and addresses in bytes.
    39  const (
    40  	// HashLength is the expected length of the hash
    41  	HashLength = 32
    42  	// AddressLength is the expected length of the address
    43  	AddressLength = 20
    44  	// length of the hash returned by Private Transaction Manager
    45  	EncryptedPayloadHashLength = 64
    46  )
    47  
    48  var (
    49  	ErrNotPrivateContract = errors.New("the provided address is not a private contract")
    50  	ErrNoAccountExtraData = errors.New("no account extra data found")
    51  
    52  	hashT    = reflect.TypeOf(Hash{})
    53  	addressT = reflect.TypeOf(Address{})
    54  )
    55  
    56  // Hash, returned by Private Transaction Manager, represents the 64-byte hash of encrypted payload
    57  type EncryptedPayloadHash [EncryptedPayloadHashLength]byte
    58  
    59  // Using map to enable fast lookup
    60  type EncryptedPayloadHashes map[EncryptedPayloadHash]struct{}
    61  
    62  func (h *EncryptedPayloadHash) MarshalJSON() (j []byte, err error) {
    63  	return json.Marshal(h.ToBase64())
    64  }
    65  
    66  func (h *EncryptedPayloadHash) UnmarshalJSON(j []byte) (err error) {
    67  	var ephStr string
    68  	err = json.Unmarshal(j, &ephStr)
    69  	if err != nil {
    70  		return err
    71  	}
    72  	eph, err := Base64ToEncryptedPayloadHash(ephStr)
    73  	if err != nil {
    74  		return err
    75  	}
    76  	h.SetBytes(eph.Bytes())
    77  	return nil
    78  }
    79  
    80  func (h *EncryptedPayloadHashes) MarshalJSON() (j []byte, err error) {
    81  	return json.Marshal(h.ToBase64s())
    82  }
    83  
    84  func (h *EncryptedPayloadHashes) UnmarshalJSON(j []byte) (err error) {
    85  	var ephStrArray []string
    86  	err = json.Unmarshal(j, &ephStrArray)
    87  	if err != nil {
    88  		return err
    89  	}
    90  	for _, str := range ephStrArray {
    91  		eph, err := Base64ToEncryptedPayloadHash(str)
    92  		if err != nil {
    93  			return err
    94  		}
    95  		h.Add(eph)
    96  	}
    97  	return nil
    98  }
    99  
   100  // BytesToEncryptedPayloadHash sets b to EncryptedPayloadHash.
   101  // If b is larger than len(h), b will be cropped from the left.
   102  func BytesToEncryptedPayloadHash(b []byte) EncryptedPayloadHash {
   103  	var h EncryptedPayloadHash
   104  	h.SetBytes(b)
   105  	return h
   106  }
   107  
   108  func Base64ToEncryptedPayloadHash(b64 string) (EncryptedPayloadHash, error) {
   109  	bytes, err := base64.StdEncoding.DecodeString(b64)
   110  	if err != nil {
   111  		return EncryptedPayloadHash{}, fmt.Errorf("unable to convert base64 string %s to EncryptedPayloadHash. Cause: %v", b64, err)
   112  	}
   113  	return BytesToEncryptedPayloadHash(bytes), nil
   114  }
   115  
   116  func (eph *EncryptedPayloadHash) SetBytes(b []byte) {
   117  	if len(b) > len(eph) {
   118  		b = b[len(b)-EncryptedPayloadHashLength:]
   119  	}
   120  
   121  	copy(eph[EncryptedPayloadHashLength-len(b):], b)
   122  }
   123  
   124  func (eph EncryptedPayloadHash) Hex() string {
   125  	return hexutil.Encode(eph[:])
   126  }
   127  
   128  func (eph EncryptedPayloadHash) Bytes() []byte {
   129  	return eph[:]
   130  }
   131  
   132  func (eph EncryptedPayloadHash) String() string {
   133  	return eph.Hex()
   134  }
   135  
   136  func (eph EncryptedPayloadHash) ToBase64() string {
   137  	return base64.StdEncoding.EncodeToString(eph[:])
   138  }
   139  
   140  func (eph EncryptedPayloadHash) TerminalString() string {
   141  	return fmt.Sprintf("%x…%x", eph[:3], eph[EncryptedPayloadHashLength-3:])
   142  }
   143  
   144  func (eph EncryptedPayloadHash) BytesTypeRef() *hexutil.Bytes {
   145  	b := hexutil.Bytes(eph.Bytes())
   146  	return &b
   147  }
   148  
   149  func EmptyEncryptedPayloadHash(eph EncryptedPayloadHash) bool {
   150  	return eph == EncryptedPayloadHash{}
   151  }
   152  
   153  // Hash represents the 32 byte Keccak256 hash of arbitrary data.
   154  type Hash [HashLength]byte
   155  
   156  // BytesToHash sets b to hash.
   157  // If b is larger than len(h), b will be cropped from the left.
   158  func BytesToHash(b []byte) Hash {
   159  	var h Hash
   160  	h.SetBytes(b)
   161  	return h
   162  }
   163  
   164  func StringToHash(s string) Hash { return BytesToHash([]byte(s)) } // dep: Istanbul
   165  
   166  // BigToHash sets byte representation of b to hash.
   167  // If b is larger than len(h), b will be cropped from the left.
   168  func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) }
   169  
   170  // HexToHash sets byte representation of s to hash.
   171  // If b is larger than len(h), b will be cropped from the left.
   172  func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) }
   173  
   174  // Bytes gets the byte representation of the underlying hash.
   175  func (h Hash) Bytes() []byte { return h[:] }
   176  
   177  // Big converts a hash to a big integer.
   178  func (h Hash) Big() *big.Int { return new(big.Int).SetBytes(h[:]) }
   179  
   180  // Hex converts a hash to a hex string.
   181  func (h Hash) Hex() string { return hexutil.Encode(h[:]) }
   182  
   183  // TerminalString implements log.TerminalStringer, formatting a string for console
   184  // output during logging.
   185  func (h Hash) TerminalString() string {
   186  	return fmt.Sprintf("%x..%x", h[:3], h[29:])
   187  }
   188  
   189  // String implements the stringer interface and is used also by the logger when
   190  // doing full logging into a file.
   191  func (h Hash) String() string {
   192  	return h.Hex()
   193  }
   194  
   195  // Format implements fmt.Formatter.
   196  // Hash supports the %v, %s, %v, %x, %X and %d format verbs.
   197  func (h Hash) Format(s fmt.State, c rune) {
   198  	hexb := make([]byte, 2+len(h)*2)
   199  	copy(hexb, "0x")
   200  	hex.Encode(hexb[2:], h[:])
   201  
   202  	switch c {
   203  	case 'x', 'X':
   204  		if !s.Flag('#') {
   205  			hexb = hexb[2:]
   206  		}
   207  		if c == 'X' {
   208  			hexb = bytes.ToUpper(hexb)
   209  		}
   210  		fallthrough
   211  	case 'v', 's':
   212  		s.Write(hexb)
   213  	case 'q':
   214  		q := []byte{'"'}
   215  		s.Write(q)
   216  		s.Write(hexb)
   217  		s.Write(q)
   218  	case 'd':
   219  		fmt.Fprint(s, ([len(h)]byte)(h))
   220  	default:
   221  		fmt.Fprintf(s, "%%!%c(hash=%x)", c, h)
   222  	}
   223  }
   224  
   225  // UnmarshalText parses a hash in hex syntax.
   226  func (h *Hash) UnmarshalText(input []byte) error {
   227  	return hexutil.UnmarshalFixedText("Hash", input, h[:])
   228  }
   229  
   230  // UnmarshalJSON parses a hash in hex syntax.
   231  func (h *Hash) UnmarshalJSON(input []byte) error {
   232  	return hexutil.UnmarshalFixedJSON(hashT, input, h[:])
   233  }
   234  
   235  // MarshalText returns the hex representation of h.
   236  func (h Hash) MarshalText() ([]byte, error) {
   237  	return hexutil.Bytes(h[:]).MarshalText()
   238  }
   239  
   240  // SetBytes sets the hash to the value of b.
   241  // If b is larger than len(h), b will be cropped from the left.
   242  func (h *Hash) SetBytes(b []byte) {
   243  	if len(b) > len(h) {
   244  		b = b[len(b)-HashLength:]
   245  	}
   246  
   247  	copy(h[HashLength-len(b):], b)
   248  }
   249  
   250  func EmptyHash(h Hash) bool {
   251  	return h == Hash{}
   252  }
   253  
   254  // Generate implements testing/quick.Generator.
   255  func (h Hash) Generate(rand *rand.Rand, size int) reflect.Value {
   256  	m := rand.Intn(len(h))
   257  	for i := len(h) - 1; i > m; i-- {
   258  		h[i] = byte(rand.Uint32())
   259  	}
   260  	return reflect.ValueOf(h)
   261  }
   262  
   263  func (h Hash) ToBase64() string {
   264  	return base64.StdEncoding.EncodeToString(h.Bytes())
   265  }
   266  
   267  // Decode base64 string to Hash
   268  // if String is empty then return empty hash
   269  func Base64ToHash(b64 string) (Hash, error) {
   270  	if b64 == "" {
   271  		return Hash{}, nil
   272  	}
   273  	bytes, err := base64.StdEncoding.DecodeString(b64)
   274  	if err != nil {
   275  		return Hash{}, fmt.Errorf("unable to convert base64 string %s to Hash. Cause: %v", b64, err)
   276  	}
   277  	return BytesToHash(bytes), nil
   278  }
   279  
   280  // Scan implements Scanner for database/sql.
   281  func (h *Hash) Scan(src interface{}) error {
   282  	srcB, ok := src.([]byte)
   283  	if !ok {
   284  		return fmt.Errorf("can't scan %T into Hash", src)
   285  	}
   286  	if len(srcB) != HashLength {
   287  		return fmt.Errorf("can't scan []byte of len %d into Hash, want %d", len(srcB), HashLength)
   288  	}
   289  	copy(h[:], srcB)
   290  	return nil
   291  }
   292  
   293  // Value implements valuer for database/sql.
   294  func (h Hash) Value() (driver.Value, error) {
   295  	return h[:], nil
   296  }
   297  
   298  // ImplementsGraphQLType returns true if Hash implements the specified GraphQL type.
   299  func (Hash) ImplementsGraphQLType(name string) bool { return name == "Bytes32" }
   300  
   301  // UnmarshalGraphQL unmarshals the provided GraphQL query data.
   302  func (h *Hash) UnmarshalGraphQL(input interface{}) error {
   303  	var err error
   304  	switch input := input.(type) {
   305  	case string:
   306  		err = h.UnmarshalText([]byte(input))
   307  	default:
   308  		err = fmt.Errorf("unexpected type %T for Hash", input)
   309  	}
   310  	return err
   311  }
   312  
   313  // UnprefixedHash allows marshaling a Hash without 0x prefix.
   314  type UnprefixedHash Hash
   315  
   316  // UnmarshalText decodes the hash from hex. The 0x prefix is optional.
   317  func (h *UnprefixedHash) UnmarshalText(input []byte) error {
   318  	return hexutil.UnmarshalFixedUnprefixedText("UnprefixedHash", input, h[:])
   319  }
   320  
   321  // MarshalText encodes the hash as hex.
   322  func (h UnprefixedHash) MarshalText() ([]byte, error) {
   323  	return []byte(hex.EncodeToString(h[:])), nil
   324  }
   325  
   326  func (ephs EncryptedPayloadHashes) ToBase64s() []string {
   327  	a := make([]string, 0, len(ephs))
   328  	for eph := range ephs {
   329  		a = append(a, eph.ToBase64())
   330  	}
   331  	return a
   332  }
   333  
   334  func (ephs EncryptedPayloadHashes) NotExist(eph EncryptedPayloadHash) bool {
   335  	_, ok := ephs[eph]
   336  	return !ok
   337  }
   338  
   339  func (ephs EncryptedPayloadHashes) Add(eph EncryptedPayloadHash) {
   340  	ephs[eph] = struct{}{}
   341  }
   342  
   343  func (ephs EncryptedPayloadHashes) EncodeRLP(writer io.Writer) error {
   344  	encryptedPayloadHashesArray := make([]EncryptedPayloadHash, len(ephs))
   345  	idx := 0
   346  	for key := range ephs {
   347  		encryptedPayloadHashesArray[idx] = key
   348  		idx++
   349  	}
   350  	return rlp.Encode(writer, encryptedPayloadHashesArray)
   351  }
   352  
   353  func (ephs EncryptedPayloadHashes) DecodeRLP(stream *rlp.Stream) error {
   354  	var encryptedPayloadHashesRLP []EncryptedPayloadHash
   355  	if err := stream.Decode(&encryptedPayloadHashesRLP); err != nil {
   356  		return err
   357  	}
   358  	for _, val := range encryptedPayloadHashesRLP {
   359  		ephs.Add(val)
   360  	}
   361  	return nil
   362  }
   363  
   364  func Base64sToEncryptedPayloadHashes(b64s []string) (EncryptedPayloadHashes, error) {
   365  	ephs := make(EncryptedPayloadHashes)
   366  	for _, b64 := range b64s {
   367  		data, err := Base64ToEncryptedPayloadHash(b64)
   368  		if err != nil {
   369  			return nil, err
   370  		}
   371  		ephs.Add(data)
   372  	}
   373  	return ephs, nil
   374  }
   375  
   376  // Print hex but only first 3 and last 3 bytes
   377  func FormatTerminalString(data []byte) string {
   378  	l := len(data)
   379  	if l > 0 {
   380  		if l > 6 {
   381  			return fmt.Sprintf("%x…%x", data[:3], data[l-3:])
   382  		} else {
   383  			return fmt.Sprintf("%x", data[:])
   384  		}
   385  	}
   386  	return ""
   387  }
   388  
   389  /////////// Address
   390  
   391  // Address represents the 20 byte address of an Ethereum account.
   392  type Address [AddressLength]byte
   393  
   394  // BytesToAddress returns Address with value b.
   395  // If b is larger than len(h), b will be cropped from the left.
   396  func BytesToAddress(b []byte) Address {
   397  	var a Address
   398  	a.SetBytes(b)
   399  	return a
   400  }
   401  
   402  func StringToAddress(s string) Address { return BytesToAddress([]byte(s)) } // dep: Istanbul
   403  
   404  // BigToAddress returns Address with byte values of b.
   405  // If b is larger than len(h), b will be cropped from the left.
   406  func BigToAddress(b *big.Int) Address { return BytesToAddress(b.Bytes()) }
   407  
   408  // HexToAddress returns Address with byte values of s.
   409  // If s is larger than len(h), s will be cropped from the left.
   410  func HexToAddress(s string) Address { return BytesToAddress(FromHex(s)) }
   411  
   412  // IsHexAddress verifies whether a string can represent a valid hex-encoded
   413  // Ethereum address or not.
   414  func IsHexAddress(s string) bool {
   415  	if has0xPrefix(s) {
   416  		s = s[2:]
   417  	}
   418  	return len(s) == 2*AddressLength && isHex(s)
   419  }
   420  
   421  // Bytes gets the string representation of the underlying address.
   422  func (a Address) Bytes() []byte { return a[:] }
   423  
   424  // Hash converts an address to a hash by left-padding it with zeros.
   425  func (a Address) Hash() Hash { return BytesToHash(a[:]) }
   426  
   427  // Hex returns an EIP55-compliant hex string representation of the address.
   428  func (a Address) Hex() string {
   429  	return string(a.checksumHex())
   430  }
   431  
   432  // String implements fmt.Stringer.
   433  func (a Address) String() string {
   434  	return a.Hex()
   435  }
   436  
   437  func (a *Address) checksumHex() []byte {
   438  	buf := a.hex()
   439  
   440  	// compute checksum
   441  	sha := sha3.NewLegacyKeccak256()
   442  	sha.Write(buf[2:])
   443  	hash := sha.Sum(nil)
   444  	for i := 2; i < len(buf); i++ {
   445  		hashByte := hash[(i-2)/2]
   446  		if i%2 == 0 {
   447  			hashByte = hashByte >> 4
   448  		} else {
   449  			hashByte &= 0xf
   450  		}
   451  		if buf[i] > '9' && hashByte > 7 {
   452  			buf[i] -= 32
   453  		}
   454  	}
   455  	return buf[:]
   456  }
   457  
   458  func (a Address) hex() []byte {
   459  	var buf [len(a)*2 + 2]byte
   460  	copy(buf[:2], "0x")
   461  	hex.Encode(buf[2:], a[:])
   462  	return buf[:]
   463  }
   464  
   465  // Format implements fmt.Formatter.
   466  // Address supports the %v, %s, %v, %x, %X and %d format verbs.
   467  func (a Address) Format(s fmt.State, c rune) {
   468  	switch c {
   469  	case 'v', 's':
   470  		s.Write(a.checksumHex())
   471  	case 'q':
   472  		q := []byte{'"'}
   473  		s.Write(q)
   474  		s.Write(a.checksumHex())
   475  		s.Write(q)
   476  	case 'x', 'X':
   477  		// %x disables the checksum.
   478  		hex := a.hex()
   479  		if !s.Flag('#') {
   480  			hex = hex[2:]
   481  		}
   482  		if c == 'X' {
   483  			hex = bytes.ToUpper(hex)
   484  		}
   485  		s.Write(hex)
   486  	case 'd':
   487  		fmt.Fprint(s, ([len(a)]byte)(a))
   488  	default:
   489  		fmt.Fprintf(s, "%%!%c(address=%x)", c, a)
   490  	}
   491  }
   492  
   493  // SetBytes sets the address to the value of b.
   494  // If b is larger than len(a), b will be cropped from the left.
   495  func (a *Address) SetBytes(b []byte) {
   496  	if len(b) > len(a) {
   497  		b = b[len(b)-AddressLength:]
   498  	}
   499  	copy(a[AddressLength-len(b):], b)
   500  }
   501  
   502  // MarshalText returns the hex representation of a.
   503  func (a Address) MarshalText() ([]byte, error) {
   504  	return hexutil.Bytes(a[:]).MarshalText()
   505  }
   506  
   507  // UnmarshalText parses a hash in hex syntax.
   508  func (a *Address) UnmarshalText(input []byte) error {
   509  	return hexutil.UnmarshalFixedText("Address", input, a[:])
   510  }
   511  
   512  // UnmarshalJSON parses a hash in hex syntax.
   513  func (a *Address) UnmarshalJSON(input []byte) error {
   514  	return hexutil.UnmarshalFixedJSON(addressT, input, a[:])
   515  }
   516  
   517  // Scan implements Scanner for database/sql.
   518  func (a *Address) Scan(src interface{}) error {
   519  	srcB, ok := src.([]byte)
   520  	if !ok {
   521  		return fmt.Errorf("can't scan %T into Address", src)
   522  	}
   523  	if len(srcB) != AddressLength {
   524  		return fmt.Errorf("can't scan []byte of len %d into Address, want %d", len(srcB), AddressLength)
   525  	}
   526  	copy(a[:], srcB)
   527  	return nil
   528  }
   529  
   530  // Value implements valuer for database/sql.
   531  func (a Address) Value() (driver.Value, error) {
   532  	return a[:], nil
   533  }
   534  
   535  // ImplementsGraphQLType returns true if Hash implements the specified GraphQL type.
   536  func (a Address) ImplementsGraphQLType(name string) bool { return name == "Address" }
   537  
   538  // UnmarshalGraphQL unmarshals the provided GraphQL query data.
   539  func (a *Address) UnmarshalGraphQL(input interface{}) error {
   540  	var err error
   541  	switch input := input.(type) {
   542  	case string:
   543  		err = a.UnmarshalText([]byte(input))
   544  	default:
   545  		err = fmt.Errorf("unexpected type %T for Address", input)
   546  	}
   547  	return err
   548  }
   549  
   550  // UnprefixedAddress allows marshaling an Address without 0x prefix.
   551  type UnprefixedAddress Address
   552  
   553  // UnmarshalText decodes the address from hex. The 0x prefix is optional.
   554  func (a *UnprefixedAddress) UnmarshalText(input []byte) error {
   555  	return hexutil.UnmarshalFixedUnprefixedText("UnprefixedAddress", input, a[:])
   556  }
   557  
   558  // MarshalText encodes the address as hex.
   559  func (a UnprefixedAddress) MarshalText() ([]byte, error) {
   560  	return []byte(hex.EncodeToString(a[:])), nil
   561  }
   562  
   563  // MixedcaseAddress retains the original string, which may or may not be
   564  // correctly checksummed
   565  type MixedcaseAddress struct {
   566  	addr     Address
   567  	original string
   568  }
   569  
   570  // NewMixedcaseAddress constructor (mainly for testing)
   571  func NewMixedcaseAddress(addr Address) MixedcaseAddress {
   572  	return MixedcaseAddress{addr: addr, original: addr.Hex()}
   573  }
   574  
   575  // NewMixedcaseAddressFromString is mainly meant for unit-testing
   576  func NewMixedcaseAddressFromString(hexaddr string) (*MixedcaseAddress, error) {
   577  	if !IsHexAddress(hexaddr) {
   578  		return nil, errors.New("invalid address")
   579  	}
   580  	a := FromHex(hexaddr)
   581  	return &MixedcaseAddress{addr: BytesToAddress(a), original: hexaddr}, nil
   582  }
   583  
   584  // UnmarshalJSON parses MixedcaseAddress
   585  func (ma *MixedcaseAddress) UnmarshalJSON(input []byte) error {
   586  	if err := hexutil.UnmarshalFixedJSON(addressT, input, ma.addr[:]); err != nil {
   587  		return err
   588  	}
   589  	return json.Unmarshal(input, &ma.original)
   590  }
   591  
   592  // MarshalJSON marshals the original value
   593  func (ma *MixedcaseAddress) MarshalJSON() ([]byte, error) {
   594  	if strings.HasPrefix(ma.original, "0x") || strings.HasPrefix(ma.original, "0X") {
   595  		return json.Marshal(fmt.Sprintf("0x%s", ma.original[2:]))
   596  	}
   597  	return json.Marshal(fmt.Sprintf("0x%s", ma.original))
   598  }
   599  
   600  // Address returns the address
   601  func (ma *MixedcaseAddress) Address() Address {
   602  	return ma.addr
   603  }
   604  
   605  // String implements fmt.Stringer
   606  func (ma *MixedcaseAddress) String() string {
   607  	if ma.ValidChecksum() {
   608  		return fmt.Sprintf("%s [chksum ok]", ma.original)
   609  	}
   610  	return fmt.Sprintf("%s [chksum INVALID]", ma.original)
   611  }
   612  
   613  // ValidChecksum returns true if the address has valid checksum
   614  func (ma *MixedcaseAddress) ValidChecksum() bool {
   615  	return ma.original == ma.addr.Hex()
   616  }
   617  
   618  // Original returns the mixed-case input string
   619  func (ma *MixedcaseAddress) Original() string {
   620  	return ma.original
   621  }
   622  
   623  type DecryptRequest struct {
   624  	SenderKey       []byte   `json:"senderKey"`
   625  	CipherText      []byte   `json:"cipherText"`
   626  	CipherTextNonce []byte   `json:"cipherTextNonce"`
   627  	RecipientBoxes  []string `json:"recipientBoxes"`
   628  	RecipientNonce  []byte   `json:"recipientNonce"`
   629  	RecipientKeys   []string `json:"recipientKeys"`
   630  }