Skip to content

Commit

Permalink
Initial work on supporting WebAuthN public key in eos-go
Browse files Browse the repository at this point in the history
Added support for public WebAuthN key as well as signature handling. For now, verification
of signature against message is left for another time, but it seems it should be
possible.
  • Loading branch information
Matthieu Vachon committed Jan 22, 2020
1 parent 76efcfb commit 4985cf2
Show file tree
Hide file tree
Showing 22 changed files with 397 additions and 66 deletions.
39 changes: 39 additions & 0 deletions btcsuite/btcutil/base58/base58.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
23 changes: 22 additions & 1 deletion btcsuite/btcutil/base58/base58_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}
}
Expand Down
71 changes: 63 additions & 8 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
25 changes: 23 additions & 2 deletions decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package eos
import (
"encoding/binary"
"encoding/hex"
"fmt"
"testing"

"bytes"
Expand Down Expand Up @@ -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{}}
Expand All @@ -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)
Expand Down Expand Up @@ -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]")
Expand Down
2 changes: 1 addition & 1 deletion ecc/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")},
}

Expand Down
3 changes: 3 additions & 0 deletions ecc/curve.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type CurveID uint8
const (
CurveK1 = CurveID(iota)
CurveR1
CurveWA
)

func (c CurveID) String() string {
Expand All @@ -13,6 +14,8 @@ func (c CurveID) String() string {
return "K1"
case CurveR1:
return "R1"
case CurveWA:
return "WA"
default:
return "UN" // unknown
}
Expand Down
5 changes: 5 additions & 0 deletions ecc/privkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
File renamed without changes.
File renamed without changes.
25 changes: 25 additions & 0 deletions ecc/privkey_wa.go
Original file line number Diff line number Diff line change
@@ -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"
}
Loading

0 comments on commit 4985cf2

Please sign in to comment.