diff --git a/btcsuite/btcutil/base58/base58.go b/btcsuite/btcutil/base58/base58.go index bb940e38..d1b46fb9 100644 --- a/btcsuite/btcutil/base58/base58.go +++ b/btcsuite/btcutil/base58/base58.go @@ -45,6 +45,45 @@ func Decode(b string) []byte { return val } +// DecodeVarSize decode a base58 whose size is not fixed +// +// Converted to Golang from Typescript +// @see https://github.com/EOSIO/eosjs/blob/wa-experiment/src/eosjs-numeric.ts#L127 +func DecodeVarSize(s string) []byte { + var result []byte + for _, c := range s { + carry := b58[int(c)] + if carry < 0 { + return nil + } + + for j, element := range result { + x := int(element)*58 + int(carry) + result[j] = byte(x & 0xff) + carry = byte(x >> 8) + } + + if carry > 0 { + result = append(result, carry) + } + } + + for _, c := range s { + if c == '1' { + result = append(result, 0) + } else { + break + } + } + + // Reverse the results array + for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 { + result[i], result[j] = result[j], result[i] + } + + return result +} + // Encode encodes a byte slice to a modified base58 string. func Encode(b []byte) string { x := new(big.Int) diff --git a/btcsuite/btcutil/base58/base58_test.go b/btcsuite/btcutil/base58/base58_test.go index feecb596..1fc9e203 100644 --- a/btcsuite/btcutil/base58/base58_test.go +++ b/btcsuite/btcutil/base58/base58_test.go @@ -62,6 +62,13 @@ var hexTests = []struct { {"00000000000000000000", "1111111111"}, } +var hexVarTests = []struct { + in string + out string +}{ + {"02364ee3c86f1a4159576e46078431a9906b44ec2bdc720ec4dbea4afae0ac643b01096c6f63616c686f737417008178", "5hyixc7vkMbKiThWi1TnFtXw7HTDcHfjREj2SzxCtgw3jQGepa5T9VHEy1Tunjzzj"}, +} + func TestBase58(t *testing.T) { // Encode tests for x, test := range stringTests { @@ -82,7 +89,21 @@ func TestBase58(t *testing.T) { } if res := base58.Decode(test.out); !bytes.Equal(res, b) { t.Errorf("Decode test #%d failed: got: %q want: %q", - x, res, test.in) + x, hex.EncodeToString(res), test.in) + continue + } + } + + // DecodeVarSize tests + for x, test := range hexVarTests { + b, err := hex.DecodeString(test.in) + if err != nil { + t.Errorf("hex.DecodeString failed failed #%d: got: %s", x, test.in) + continue + } + if res := base58.DecodeVarSize(test.out); !bytes.Equal(res, b) { + t.Errorf("DecodeVarSize test #%d failed: got: %q want: %q", + x, hex.EncodeToString(res), test.in) continue } } diff --git a/decoder.go b/decoder.go index ea89e8f0..53962daf 100644 --- a/decoder.go +++ b/decoder.go @@ -737,23 +737,78 @@ func (d *Decoder) ReadChecksum512() (out Checksum512, err error) { } func (d *Decoder) ReadPublicKey() (out ecc.PublicKey, err error) { + typeID, err := d.ReadUInt8() + if err != nil { + return out, fmt.Errorf("unable to read public key type: %s", err) + } - if d.remaining() < TypeSize.PublicKey { - err = fmt.Errorf("publicKey required [%d] bytes, remaining [%d]", TypeSize.PublicKey, d.remaining()) - return + curveID := ecc.CurveID(typeID) + var keyMaterial []byte + + if curveID == ecc.CurveK1 || curveID == ecc.CurveR1 { + // Minus 1 because we already read the curveID which is 1 out of the 34 bytes of a full "legacy" PublicKey + keyMaterial, err = d.readPublicKeyMaterial(curveID, TypeSize.PublicKey-1) + } else if curveID == ecc.CurveWA { + keyMaterial, err = d.readWAPublicKeyMaterial() + } else { + err = fmt.Errorf("unsupported curve ID") } - keyContent := make([]byte, 34) - copy(keyContent, d.data[d.pos:d.pos+TypeSize.PublicKey]) - out, err = ecc.NewPublicKeyFromData(keyContent) if err != nil { - err = fmt.Errorf("publicKey: key from data: %s", err) + return out, fmt.Errorf("unable to read public key material for curve %s: %s", curveID, err) + } + + data := append([]byte{byte(curveID)}, keyMaterial...) + out, err = ecc.NewPublicKeyFromData(data) + if err != nil { + return out, fmt.Errorf("new public key from data: %s", err) } - d.pos += TypeSize.PublicKey if loggingEnabled { decoderLog.Debug("read public key", zap.Stringer("pubkey", out)) } + + return +} + +func (d *Decoder) readPublicKeyMaterial(curveID ecc.CurveID, keyMaterialSize int) (out []byte, err error) { + if d.remaining() < keyMaterialSize { + err = fmt.Errorf("publicKey %s key material requires [%d] bytes, remaining [%d]", curveID, keyMaterialSize, d.remaining()) + return + } + + out = make([]byte, keyMaterialSize) + copy(out, d.data[d.pos:d.pos+keyMaterialSize]) + d.pos += keyMaterialSize + + return +} + +func (d *Decoder) readWAPublicKeyMaterial() (out []byte, err error) { + begin := d.pos + fmt.Println("begin", d.pos) + if d.remaining() < 35 { + err = fmt.Errorf("publicKey WA key material requires at least [35] bytes, remaining [%d]", d.remaining()) + return + } + + d.pos += 34 + reminderDataSize, err := d.ReadUvarint32() + if err != nil { + return out, fmt.Errorf("unable to read public key WA key material size: %s", err) + } + + if d.remaining() < int(reminderDataSize) { + err = fmt.Errorf("publicKey WA reminder key material requires [%d] bytes, remaining [%d]", reminderDataSize, d.remaining()) + return + } + + d.pos += int(reminderDataSize) + keyMaterialSize := d.pos - begin + + out = make([]byte, keyMaterialSize) + copy(out, d.data[begin:begin+keyMaterialSize]) + return } diff --git a/decoder_test.go b/decoder_test.go index 53d78468..8e8f0713 100644 --- a/decoder_test.go +++ b/decoder_test.go @@ -3,6 +3,7 @@ package eos import ( "encoding/binary" "encoding/hex" + "fmt" "testing" "bytes" @@ -302,6 +303,27 @@ func TestDecoder_PublicKey_R1(t *testing.T) { assert.Equal(t, 0, d.remaining()) } +func TestDecoder_PublicKey_WA(t *testing.T) { + pk := ecc.MustNewPublicKey("PUB_WA_5hyixc7vkMbKiThWi1TnFtXw7HTDcHfjREj2SzxCtgw3jQGepa5T9VHEy1Tunjzzj") + + buf := new(bytes.Buffer) + enc := NewEncoder(buf) + assert.NoError(t, enc.writePublicKey(pk)) + + fmt.Println(hex.EncodeToString(buf.Bytes())) + + d := NewDecoder(buf.Bytes()) + + rpk, err := d.ReadPublicKey() + fmt.Println(hex.EncodeToString([]byte{byte(rpk.Curve)})) + fmt.Println(hex.EncodeToString(rpk.Content)) + + require.NoError(t, err) + + assert.Equal(t, pk, rpk) + assert.Equal(t, 0, d.remaining()) +} + func TestDecoder_Empty_PublicKey(t *testing.T) { pk := ecc.PublicKey{Curve: ecc.CurveK1, Content: []byte{}} @@ -312,7 +334,6 @@ func TestDecoder_Empty_PublicKey(t *testing.T) { } func TestDecoder_Signature(t *testing.T) { - sig := ecc.MustNewSignatureFromData(bytes.Repeat([]byte{0}, 66)) buf := new(bytes.Buffer) @@ -664,7 +685,7 @@ func TestDecoder_readUint16_missing_data(t *testing.T) { assert.EqualError(t, err, "checksum 256 required [32] bytes, remaining [0]") _, err = NewDecoder([]byte{}).ReadPublicKey() - assert.EqualError(t, err, "publicKey required [34] bytes, remaining [0]") + assert.EqualError(t, err, "unable to read public key type: byte required [1] byte, remaining [0]") _, err = NewDecoder([]byte{}).ReadSignature() assert.EqualError(t, err, "signature required [66] bytes, remaining [0]") diff --git a/ecc/crypto_test.go b/ecc/crypto_test.go index 71fd9398..df2a3cf3 100644 --- a/ecc/crypto_test.go +++ b/ecc/crypto_test.go @@ -102,7 +102,7 @@ func TestPublicKeyValidity(t *testing.T) { err error }{ {"EOS859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2HqhToVM", nil}, - {"MMM859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2HqhToVM", fmt.Errorf("public key should start with [\"PUB_K1_\" | \"PUB_R1_\"] (or the old \"EOS\")")}, + {"MMM859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2HqhToVM", fmt.Errorf("public key should start with [\"PUB_K1_\" | \"PUB_R1_\" | \"PUB_WA_\"] (or the old \"EOS\")")}, {"EOS859gxfnXyUriMgUeThh1fWv3oqcpLFyHa3TfFYC4PK2HqhTo", fmt.Errorf("checkDecode: invalid checksum")}, } diff --git a/ecc/curve.go b/ecc/curve.go index 02de3c5a..3934e1ce 100644 --- a/ecc/curve.go +++ b/ecc/curve.go @@ -5,6 +5,7 @@ type CurveID uint8 const ( CurveK1 = CurveID(iota) CurveR1 + CurveWA ) func (c CurveID) String() string { @@ -13,6 +14,8 @@ func (c CurveID) String() string { return "K1" case CurveR1: return "R1" + case CurveWA: + return "WA" default: return "UN" // unknown } diff --git a/ecc/privkey.go b/ecc/privkey.go index 209f9826..305ea8db 100644 --- a/ecc/privkey.go +++ b/ecc/privkey.go @@ -62,6 +62,11 @@ func NewPrivateKey(wif string) (*PrivateKey, error) { inner := &innerR1PrivateKey{} return &PrivateKey{Curve: CurveR1, inner: inner}, nil + case "WA_": + + inner := &innerWAPrivateKey{} + return &PrivateKey{Curve: CurveWA, inner: inner}, nil + default: return nil, fmt.Errorf("unsupported curve prefix %q", curvePrefix) } diff --git a/ecc/privkeyk1.go b/ecc/privkey_k1.go similarity index 100% rename from ecc/privkeyk1.go rename to ecc/privkey_k1.go diff --git a/ecc/privkeyr1.go b/ecc/privkey_r1.go similarity index 100% rename from ecc/privkeyr1.go rename to ecc/privkey_r1.go diff --git a/ecc/privkey_wa.go b/ecc/privkey_wa.go new file mode 100644 index 00000000..58c13ff6 --- /dev/null +++ b/ecc/privkey_wa.go @@ -0,0 +1,25 @@ +package ecc + +import ( + "bytes" + "fmt" +) + +type innerWAPrivateKey struct { +} + +func (k *innerWAPrivateKey) publicKey() PublicKey { + //TODO: Finish WA support here - for now we return bogus key + var pubKeyData []byte + pubKeyData = append(pubKeyData, byte(1)) + pubKeyData = append(pubKeyData, bytes.Repeat([]byte{0}, 33)...) + return PublicKey{Curve: CurveWA, Content: pubKeyData, inner: &innerWAPublicKey{}} +} + +func (k *innerWAPrivateKey) sign(hash []byte) (out Signature, err error) { + return out, fmt.Errorf("WA not supported") +} + +func (k *innerWAPrivateKey) string() string { + return "PVT_WA_PLACE_HOLDER" +} diff --git a/ecc/pubkey.go b/ecc/pubkey.go index 03dac921..b9dfc8f0 100644 --- a/ecc/pubkey.go +++ b/ecc/pubkey.go @@ -1,8 +1,8 @@ package ecc import ( - "bytes" "encoding/json" + "errors" "fmt" "strings" @@ -14,11 +14,17 @@ import ( const PublicKeyPrefix = "PUB_" const PublicKeyK1Prefix = "PUB_K1_" const PublicKeyR1Prefix = "PUB_R1_" +const PublicKeyWAPrefix = "PUB_WA_" const PublicKeyPrefixCompat = "EOS" +var publicKeyDataSize = new(int) + +func init() { *publicKeyDataSize = 33 } + type innerPublicKey interface { key(content []byte) (*btcec.PublicKey, error) prefix() string + keyMaterialSize() *int } type PublicKey struct { @@ -29,13 +35,13 @@ type PublicKey struct { } func NewPublicKeyFromData(data []byte) (out PublicKey, err error) { - if len(data) != 34 { - return out, fmt.Errorf("public key data must have a length of 33 ") + if len(data) <= 0 { + return out, errors.New("data must have at least one byte, got 0") } out = PublicKey{ Curve: CurveID(data[0]), // 1 byte - Content: data[1:], // 33 bytes + Content: data[1:], // 33 bytes for K1 & R1 keys, variable size for WA } switch out.Curve { @@ -43,11 +49,13 @@ func NewPublicKeyFromData(data []byte) (out PublicKey, err error) { out.inner = &innerK1PublicKey{} case CurveR1: out.inner = &innerR1PublicKey{} + case CurveWA: + out.inner = &innerWAPublicKey{} default: return out, fmt.Errorf("unsupported curve prefix %q", out.Curve) } - return out, nil + return out, out.Validate() } func MustNewPublicKeyFromData(data []byte) PublicKey { @@ -58,44 +66,38 @@ func MustNewPublicKeyFromData(data []byte) PublicKey { return key } +type pubkeyReaderManifest struct { + curveID CurveID + inner func() innerPublicKey +} + +var pubKeyReaderManifest = map[string]pubkeyReaderManifest{ + PublicKeyPrefixCompat: pubkeyReaderManifest{CurveK1, newInnerK1PublicKey}, + PublicKeyK1Prefix: pubkeyReaderManifest{CurveK1, newInnerK1PublicKey}, + PublicKeyR1Prefix: pubkeyReaderManifest{CurveR1, newInnerR1PublicKey}, + PublicKeyWAPrefix: pubkeyReaderManifest{CurveWA, newInnerWAPublicKey}, +} + func NewPublicKey(pubKey string) (out PublicKey, err error) { if len(pubKey) < 8 { return out, fmt.Errorf("invalid format") } - var decodedPubKey []byte - var curveID CurveID - var inner innerPublicKey - - if strings.HasPrefix(pubKey, PublicKeyR1Prefix) { - pubKeyMaterial := pubKey[len(PublicKeyR1Prefix):] // strip "PUB_R1_" - curveID = CurveR1 - decodedPubKey, err = checkDecode(pubKeyMaterial, curveID) - if err != nil { - return out, fmt.Errorf("checkDecode: %s", err) - } - inner = &innerR1PublicKey{} - } else if strings.HasPrefix(pubKey, PublicKeyK1Prefix) { - pubKeyMaterial := pubKey[len(PublicKeyK1Prefix):] // strip "PUB_K1_" - curveID = CurveK1 - decodedPubKey, err = checkDecode(pubKeyMaterial, curveID) - if err != nil { - return out, fmt.Errorf("checkDecode: %s", err) + for prefix, manifest := range pubKeyReaderManifest { + if !strings.HasPrefix(pubKey, prefix) { + continue } - inner = &innerK1PublicKey{} - } else if strings.HasPrefix(pubKey, PublicKeyPrefixCompat) { // "EOS" - pubKeyMaterial := pubKey[len(PublicKeyPrefixCompat):] // strip "EOS" - curveID = CurveK1 - decodedPubKey, err = checkDecode(pubKeyMaterial, curveID) + + pubKeyMaterial := pubKey[len(prefix):] + decodedPubKey, err := decodeKeyMaterial(pubKeyMaterial, manifest.curveID) if err != nil { return out, fmt.Errorf("checkDecode: %s", err) } - inner = &innerK1PublicKey{} - } else { - return out, fmt.Errorf("public key should start with [%q | %q] (or the old %q)", PublicKeyK1Prefix, PublicKeyR1Prefix, PublicKeyPrefixCompat) + + return PublicKey{Curve: manifest.curveID, Content: decodedPubKey, inner: manifest.inner()}, nil } - return PublicKey{Curve: curveID, Content: decodedPubKey, inner: inner}, nil + return out, fmt.Errorf("public key should start with [%q | %q | %q] (or the old %q)", PublicKeyK1Prefix, PublicKeyR1Prefix, PublicKeyWAPrefix, PublicKeyPrefixCompat) } func MustNewPublicKey(pubKey string) PublicKey { @@ -106,24 +108,23 @@ func MustNewPublicKey(pubKey string) PublicKey { return key } -// CheckDecode decodes a string that was encoded with CheckEncode and verifies the checksum. -func checkDecode(input string, curve CurveID) (result []byte, err error) { - decoded := base58.Decode(input) - if len(decoded) < 5 { - return nil, fmt.Errorf("invalid format") +func (p PublicKey) Validate() error { + if p.inner == nil { + return fmt.Errorf("the inner public key structure must be present, was nil") } - var cksum [4]byte - copy(cksum[:], decoded[len(decoded)-4:]) - ///// WARN: ok the ripemd160checksum should include the prefix in CERTAIN situations, - // like when we imported the PubKey without a prefix ?! tied to the string representation - // or something ? weird.. checksum shouldn't change based on the string reprsentation. - if bytes.Compare(ripemd160checksum(decoded[:len(decoded)-4], curve), cksum[:]) != 0 { - return nil, fmt.Errorf("invalid checksum") + + if p.Curve == CurveK1 || p.Curve == CurveR1 { + size := p.inner.keyMaterialSize() + if size == nil { + return fmt.Errorf("R1 & K1 public keys must have a fixed key material size") + } + + if len(p.Content) != *size { + return fmt.Errorf("public key data must have a length of %d, got %d", *size, len(p.Content)) + } } - // perhaps bitcoin has a leading net ID / version, but EOS doesn't - payload := decoded[:len(decoded)-4] - result = append(result, payload...) - return + + return nil } func ripemd160checksum(in []byte, curve CurveID) []byte { @@ -153,21 +154,34 @@ func (p PublicKey) Key() (*btcec.PublicKey, error) { return p.inner.key(p.Content) } +var emptyKeyMaterial = make([]byte, 33) + func (p PublicKey) String() string { data := p.Content if len(data) == 0 { - data = make([]byte, 33) + // Nothing really to do, just output some garbage + return p.inner.prefix() + base58.Encode(emptyKeyMaterial) } hash := ripemd160checksum(data, p.Curve) + size := p.KeyMaterialSize() - rawKey := make([]byte, 37) - copy(rawKey, data[:33]) - copy(rawKey[33:], hash[:4]) + rawKey := make([]byte, size+4) + copy(rawKey, data[:size]) + copy(rawKey[size:], hash[:4]) return p.inner.prefix() + base58.Encode(rawKey) } +func (p PublicKey) KeyMaterialSize() int { + size := p.inner.keyMaterialSize() + if size != nil { + return *size + } + + return len(p.Content) +} + func (p PublicKey) MarshalJSON() ([]byte, error) { s := p.String() return json.Marshal(s) diff --git a/ecc/pubkey_decoder.go b/ecc/pubkey_decoder.go new file mode 100644 index 00000000..1926bc2d --- /dev/null +++ b/ecc/pubkey_decoder.go @@ -0,0 +1,49 @@ +package ecc + +import ( + "bytes" + "fmt" + + "github.com/eoscanada/eos-go/btcsuite/btcutil/base58" +) + +var keyMaterialDecoders = map[CurveID]keyMaterialDecoder{ + CurveR1: keyMaterialDecoderFunc(base58.Decode), + CurveK1: keyMaterialDecoderFunc(base58.Decode), + CurveWA: keyMaterialDecoderFunc(base58.DecodeVarSize), +} + +// decodeKeyMaterial decodes a string that was encoded with CheckEncode and verifies the checksum. +func decodeKeyMaterial(input string, curve CurveID) (result []byte, err error) { + decoder := keyMaterialDecoders[curve] + if decoder == nil { + decoder = keyMaterialDecoderFunc(base58.Decode) + } + + decoded := decoder.Decode(input) + if len(decoded) < 5 { + return nil, fmt.Errorf("invalid format") + } + var cksum [4]byte + copy(cksum[:], decoded[len(decoded)-4:]) + ///// WARN: ok the ripemd160checksum should include the prefix in CERTAIN situations, + // like when we imported the PubKey without a prefix ?! tied to the string representation + // or something ? weird.. checksum shouldn't change based on the string reprsentation. + if bytes.Compare(ripemd160checksum(decoded[:len(decoded)-4], curve), cksum[:]) != 0 { + return nil, fmt.Errorf("invalid checksum") + } + // perhaps bitcoin has a leading net ID / version, but EOS doesn't + payload := decoded[:len(decoded)-4] + result = append(result, payload...) + return +} + +type keyMaterialDecoder interface { + Decode(input string) []byte +} + +type keyMaterialDecoderFunc func(input string) []byte + +func (f keyMaterialDecoderFunc) Decode(input string) []byte { + return f(input) +} diff --git a/ecc/pubkeyk1.go b/ecc/pubkey_k1.go similarity index 72% rename from ecc/pubkeyk1.go rename to ecc/pubkey_k1.go index 5b69b3c0..c2f7cda8 100644 --- a/ecc/pubkeyk1.go +++ b/ecc/pubkey_k1.go @@ -9,6 +9,10 @@ import ( type innerK1PublicKey struct { } +func newInnerK1PublicKey() innerPublicKey { + return &innerK1PublicKey{} +} + func (p *innerK1PublicKey) key(content []byte) (*btcec.PublicKey, error) { key, err := btcec.ParsePubKey(content, btcec.S256()) if err != nil { @@ -21,3 +25,7 @@ func (p *innerK1PublicKey) key(content []byte) (*btcec.PublicKey, error) { func (p *innerK1PublicKey) prefix() string { return PublicKeyPrefixCompat } + +func (p *innerK1PublicKey) keyMaterialSize() *int { + return publicKeyDataSize +} diff --git a/ecc/pubkeyr1.go b/ecc/pubkey_r1.go similarity index 72% rename from ecc/pubkeyr1.go rename to ecc/pubkey_r1.go index 2f1827dd..fc912448 100644 --- a/ecc/pubkeyr1.go +++ b/ecc/pubkey_r1.go @@ -9,6 +9,10 @@ import ( type innerR1PublicKey struct { } +func newInnerR1PublicKey() innerPublicKey { + return &innerR1PublicKey{} +} + func (p *innerR1PublicKey) key(content []byte) (*btcec.PublicKey, error) { key, err := btcec.ParsePubKey(content, btcec.S256()) if err != nil { @@ -21,3 +25,7 @@ func (p *innerR1PublicKey) key(content []byte) (*btcec.PublicKey, error) { func (p *innerR1PublicKey) prefix() string { return PublicKeyR1Prefix } + +func (p *innerR1PublicKey) keyMaterialSize() *int { + return publicKeyDataSize +} diff --git a/ecc/pubkey_test.go b/ecc/pubkey_test.go index ec295d34..13b4d5bf 100644 --- a/ecc/pubkey_test.go +++ b/ecc/pubkey_test.go @@ -28,6 +28,11 @@ func Test_PublicKeyMarshalUnmarshal(t *testing.T) { key: "PUB_R1_78rbUHSk87e7eCBoccgWUkhNTCZLYdvJzerDRHg6fxj2SQy6Xm", expectedKey: "PUB_R1_78rbUHSk87e7eCBoccgWUkhNTCZLYdvJzerDRHg6fxj2SQy6Xm", }, + { + name: "WA", + key: "PUB_WA_5hyixc7vkMbKiThWi1TnFtXw7HTDcHfjREj2SzxCtgw3jQGepa5T9VHEy1Tunjzzj", + expectedKey: "PUB_WA_5hyixc7vkMbKiThWi1TnFtXw7HTDcHfjREj2SzxCtgw3jQGepa5T9VHEy1Tunjzzj", + }, } for _, c := range cases { @@ -60,6 +65,11 @@ func TestPublicKey_MarshalJSON(t *testing.T) { key: "PUB_R1_78rbUHSk87e7eCBoccgWUkhNTCZLYdvJzerDRHg6fxj2SQy6Xm", expectedJSON: `"PUB_R1_78rbUHSk87e7eCBoccgWUkhNTCZLYdvJzerDRHg6fxj2SQy6Xm"`, }, + { + name: "WA", + key: "PUB_WA_5hyixc7vkMbKiThWi1TnFtXw7HTDcHfjREj2SzxCtgw3jQGepa5T9VHEy1Tunjzzj", + expectedJSON: `"PUB_WA_5hyixc7vkMbKiThWi1TnFtXw7HTDcHfjREj2SzxCtgw3jQGepa5T9VHEy1Tunjzzj"`, + }, } for _, c := range cases { diff --git a/ecc/pubkey_wa.go b/ecc/pubkey_wa.go new file mode 100644 index 00000000..d3545035 --- /dev/null +++ b/ecc/pubkey_wa.go @@ -0,0 +1,26 @@ +package ecc + +import ( + "fmt" + + "github.com/eoscanada/eos-go/btcsuite/btcd/btcec" +) + +type innerWAPublicKey struct { +} + +func newInnerWAPublicKey() innerPublicKey { + return &innerWAPublicKey{} +} + +func (p *innerWAPublicKey) key(content []byte) (*btcec.PublicKey, error) { + return nil, fmt.Errorf("") +} + +func (p *innerWAPublicKey) keyMaterialSize() *int { + return nil +} + +func (p *innerWAPublicKey) prefix() string { + return PublicKeyWAPrefix +} diff --git a/ecc/signature.go b/ecc/signature.go index ba2080ec..52900127 100644 --- a/ecc/signature.go +++ b/ecc/signature.go @@ -99,6 +99,14 @@ func NewSignature(fromText string) (Signature, error) { return Signature{Curve: CurveR1, Content: content, innerSignature: &innerR1Signature{}}, nil + case "WA_": + + fromText = fromText[3:] // strip WA_ + content := base58.DecodeVarSize(fromText) + //todo: stuff here + + return Signature{Curve: CurveWA, Content: content, innerSignature: &innerWASignature{}}, nil + default: return Signature{}, fmt.Errorf("invalid curve prefix %q", curvePrefix) } diff --git a/ecc/signaturek1.go b/ecc/signature_k1.go similarity index 100% rename from ecc/signaturek1.go rename to ecc/signature_k1.go diff --git a/ecc/signaturer1.go b/ecc/signature_r1.go similarity index 100% rename from ecc/signaturer1.go rename to ecc/signature_r1.go diff --git a/ecc/signature_test.go b/ecc/signature_test.go index 1d1389c6..7d2f2172 100644 --- a/ecc/signature_test.go +++ b/ecc/signature_test.go @@ -63,6 +63,10 @@ func TestSignatureMarshalUnmarshal(t *testing.T) { name: "R1", signature: "SIG_R1_KE33Ucjr5N3GR4ZosFh8KtGMytHHNtnmdUaSoMLJVXpVXoC8B9zfoXYrLiQJZqroe3LKciaP2uJT7Myqqoo4PZH7iSnso8", }, + { + name: "WA", + signature: "SIG_WA_28AzYsRYSSA85Q4Jjp4zkiyBA8G85AcPsHU3HUuqLkY3LooYcFiSMGGxhEQcCzAhaZJqdaUXG16p8t63sDhqh9L4xc24CDxbf81D6FW4SXGjxQSM2D7FAJSSQCogjbqJanTP5CbSF8FWyaD4pVVAs4Z9ubqNhHCkiLDesEukwGYu6ujgwQkFqczow5cSwTqTirdgqCBjkGQLMT3KV2JwjN7b2qPAyDa2vvjsGWFP8HVTw2tctD6FBPHU9nFgtfcztkc3eqxVU9UbvUbKayU62dLZBwNCwHxmyPymH5YfoJLhBkS8s", + }, } for _, c := range cases { @@ -119,6 +123,13 @@ func TestSignaturePublicKeyExtraction(t *testing.T) { chainID: "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906", expectedPubKeyError: "R1 not supported", }, + { + name: "WA", + signature: "SIG_WA_28AzYsRYSSA85Q4Jjp4zkiyBA8G85AcPsHU3HUuqLkY3LooYcFiSMGGxhEQcCzAhaZJqdaUXG16p8t63sDhqh9L4xc24CDxbf81D6FW4SXGjxQSM2D7FAJSSQCogjbqJanTP5CbSF8FWyaD4pVVAs4Z9ubqNhHCkiLDesEukwGYu6ujgwQkFqczow5cSwTqTirdgqCBjkGQLMT3KV2JwjN7b2qPAyDa2vvjsGWFP8HVTw2tctD6FBPHU9nFgtfcztkc3eqxVU9UbvUbKayU62dLZBwNCwHxmyPymH5YfoJLhBkS8s", + payload: "45e2ea5b22f87c6f74430000000001a0904b1822f330550040346aabab904b01a0904b1822f3305500000000a8ed32329d01fb5f27000000000027e2ea5b0000000082b4c2a389d911f1cef87b3f10dc38e8f5118ce5b83e160c5813447db849ea89c1d910841a3662747dd0e6e0040b1317be571384054a30f7e6851ebda9adab9c0a9394a5bb26479b697937fbe8b4a9d2780bee68334b2800000000000004454f5300000000000000000000000004454f53000000000000000000000000000000000000000004454f530000000000", + chainID: "aca376f206b8fc25a6ed44dbdc66547c36c6c33e3a119ffbeaef943642f0e906", + expectedPubKeyError: "WA not supported", + }, } for _, c := range cases { diff --git a/ecc/signature_wa.go b/ecc/signature_wa.go new file mode 100644 index 00000000..ecd1a19a --- /dev/null +++ b/ecc/signature_wa.go @@ -0,0 +1,26 @@ +package ecc + +import ( + "fmt" + + "github.com/eoscanada/eos-go/btcsuite/btcutil/base58" +) + +type innerWASignature struct { +} + +func (s innerWASignature) verify(content []byte, hash []byte, pubKey PublicKey) bool { + // It seems from my understanding that WA uses standard ECDSA P256 algorithm, so we + // should be able to verify signature of message against PublicKey. + // + // See https://thanethomson.com/2018/11/30/validating-ecdsa-signatures-golang/ + return false +} + +func (s *innerWASignature) publicKey(content []byte, hash []byte) (out PublicKey, err error) { + return out, fmt.Errorf("WA not supported") +} + +func (s innerWASignature) string(content []byte) string { + return "SIG_WA_" + base58.Encode(content) +} diff --git a/encoder.go b/encoder.go index 461b998b..128f253a 100644 --- a/encoder.go +++ b/encoder.go @@ -418,8 +418,10 @@ func (e *Encoder) writePublicKey(pk ecc.PublicKey) (err error) { if loggingEnabled { encoderLog.Debug("write public key", zap.Stringer("pubkey", pk)) } - if len(pk.Content) != 33 { - return fmt.Errorf("public key %q should be 33 bytes, was %d", hex.EncodeToString(pk.Content), len(pk.Content)) + + err = pk.Validate() + if err != nil { + return fmt.Errorf("invalid public key: %s", err) } if err = e.writeByte(byte(pk.Curve)); err != nil {