diff --git a/accounts/keystore/key.go b/accounts/keystore/key.go index 240a014535..99252b2c6b 100644 --- a/accounts/keystore/key.go +++ b/accounts/keystore/key.go @@ -21,6 +21,7 @@ import ( "crypto/ecdsa" "encoding/hex" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -29,6 +30,7 @@ import ( "strings" "time" + "github.com/btcsuite/btcd/btcec" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" @@ -272,6 +274,66 @@ func keyFileName(keyAddr common.Address) string { return fmt.Sprintf("UTC--%s--%s", toISO8601(ts), hex.EncodeToString(keyAddr[:])) } +// GeneratePKPairFromUAddress represents the keystore to retrieve public key-pair from given UAddress +func GeneratePKPairFromUAddress(w []byte) (*ecdsa.PublicKey, *ecdsa.PublicKey, error) { + if len(w) != common.UAddressLength { + return nil, nil, ErrUAddressInvalid + } + + tmp := make([]byte, 33) + copy(tmp[:], w[:33]) + curve := btcec.S256() + PK1, err := btcec.ParsePubKey(tmp, curve) + if err != nil { + return nil, nil, err + } + + copy(tmp[:], w[33:]) + PK2, err := btcec.ParsePubKey(tmp, curve) + if err != nil { + return nil, nil, err + } + + return (*ecdsa.PublicKey)(PK1), (*ecdsa.PublicKey)(PK2), nil +} + +func UaddrFromUncompressedRawBytes(raw []byte) (*common.UAddress, error) { + if len(raw) != 32*2*2 { + return nil, errors.New("invalid uncompressed use address len") + } + + pub := make([]byte, 65) + pub[0] = 0x004 + copy(pub[1:], raw[:64]) + A := crypto.ToECDSAPub(pub) + copy(pub[1:], raw[64:]) + B := crypto.ToECDSAPub(pub) + return GenerateUaddressFromPK(A, B), nil +} + +func UaddrToUncompressedRawBytes(waddr []byte) ([]byte, error) { + if len(waddr) != common.UAddressLength { + return nil, ErrUAddressInvalid + } + + A, B, err := GeneratePKPairFromUAddress(waddr) + if err != nil { + return nil, err + } + + u := make([]byte, 32*2*2) + ax := math.PaddedBigBytes(A.X, 32) + ay := math.PaddedBigBytes(A.Y, 32) + bx := math.PaddedBigBytes(B.X, 32) + by := math.PaddedBigBytes(B.Y, 32) + copy(u[0:], ax[:32]) + copy(u[32:], ay[:32]) + copy(u[64:], bx[:32]) + copy(u[96:], by[:32]) + + return u, nil +} + func toISO8601(t time.Time) string { var tz string name, offset := t.Zone() diff --git a/common/hexutil/hexutil.go b/common/hexutil/hexutil.go index 46223a2815..fc86b8670f 100644 --- a/common/hexutil/hexutil.go +++ b/common/hexutil/hexutil.go @@ -31,10 +31,13 @@ encoding may be of uneven length. The number zero encodes as "0x0". package hexutil import ( + "crypto/ecdsa" "encoding/hex" "fmt" "math/big" "strconv" + + "github.com/ethereum/go-ethereum/common/math" ) const uintBits = 32 << (uint64(^uint(0)) >> 63) @@ -238,3 +241,13 @@ func mapError(err error) error { } return err } + +// PKPair2HexSlice generate byte-slice based on given public key pair +func PKPair2HexSlice(pk1 *ecdsa.PublicKey, pk2 *ecdsa.PublicKey) []string { + return []string{ + Encode(math.PaddedBigBytes(pk1.X, 32)), + Encode(math.PaddedBigBytes(pk1.Y, 32)), + Encode(math.PaddedBigBytes(pk2.X, 32)), + Encode(math.PaddedBigBytes(pk2.Y, 32)), + } +} diff --git a/crypto/crypto.go b/crypto/crypto.go index 2869b4c191..d2303d2ade 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -29,6 +29,7 @@ import ( "os" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/rlp" "golang.org/x/crypto/sha3" @@ -148,6 +149,20 @@ func UnmarshalPubkey(pub []byte) (*ecdsa.PublicKey, error) { return &ecdsa.PublicKey{Curve: S256(), X: x, Y: y}, nil } +//check input error +func ToECDSAPub(pub []byte) *ecdsa.PublicKey { + if len(pub) != 65 { + return nil + } + + x, y := elliptic.Unmarshal(S256(), pub) + if x == nil || y == nil { + return nil + } + + return &ecdsa.PublicKey{Curve: S256(), X: x, Y: y} +} + func FromECDSAPub(pub *ecdsa.PublicKey) []byte { if pub == nil || pub.X == nil || pub.Y == nil { return nil @@ -219,3 +234,64 @@ func zeroBytes(bytes []byte) { bytes[i] = 0 } } + +// A1=[hash([r]B)]G+A +func generateA1(r []byte, A *ecdsa.PublicKey, B *ecdsa.PublicKey) ecdsa.PublicKey { + A1 := new(ecdsa.PublicKey) + A1.X, A1.Y = S256().ScalarMult(B.X, B.Y, r) //A1=[r]B + A1Bytes := Keccak256(FromECDSAPub(A1)) //hash([r]B) + A1.X, A1.Y = S256().ScalarBaseMult(A1Bytes) //[hash([r]B)]G + A1.X, A1.Y = S256().Add(A1.X, A1.Y, A.X, A.Y) //A1=[hash([r]B)]G+A + A1.Curve = S256() + return *A1 +} + +func CompareA1(b []byte, A *ecdsa.PublicKey, S1 *ecdsa.PublicKey, A1 *ecdsa.PublicKey) bool { + A1n := generateA1(b, A, S1) + if A1.X.Cmp(A1n.X) == 0 && A1.Y.Cmp(A1n.Y) == 0 { + return true + } + return false +} + +// generateOneTimeKey2528 generates an OTA account for receiver using receiver's publickey +func generateOneTimeKey2528(A *ecdsa.PublicKey, B *ecdsa.PublicKey) (A1 *ecdsa.PublicKey, R *ecdsa.PublicKey, err error) { + RPrivateKey, err := GenerateKey() + if err != nil { + return nil, nil, err + } + R = &RPrivateKey.PublicKey + A1 = new(ecdsa.PublicKey) + *A1 = generateA1(RPrivateKey.D.Bytes(), A, B) + return A1, R, err +} + +// Generate OTA account interface +func GenerateOneTimeKey(AX string, AY string, BX string, BY string) (ret []string, err error) { + bytesAX, err := hexutil.Decode(AX) + if err != nil { + return + } + bytesAY, err := hexutil.Decode(AY) + if err != nil { + return + } + bytesBX, err := hexutil.Decode(BX) + if err != nil { + return + } + bytesBY, err := hexutil.Decode(BY) + if err != nil { + return + } + bnAX := new(big.Int).SetBytes(bytesAX) + bnAY := new(big.Int).SetBytes(bytesAY) + bnBX := new(big.Int).SetBytes(bytesBX) + bnBY := new(big.Int).SetBytes(bytesBY) + + pa := &ecdsa.PublicKey{X: bnAX, Y: bnAY} + pb := &ecdsa.PublicKey{X: bnBX, Y: bnBY} + + generatedA1, generatedR, err := generateOneTimeKey2528(pa, pb) + return hexutil.PKPair2HexSlice(generatedA1, generatedR), nil +} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 7bfc6c952b..1aea97bf29 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -52,6 +52,18 @@ const ( defaultGasPrice = params.GWei ) +var ( + ErrInvalidUAddress = errors.New("Invalid Uaddress, try again") + ErrFailToGeneratePKPairFromUAddress = errors.New("Fail to generate publickey pair from UAddress") + ErrFailToGeneratePKPairSlice = errors.New("Fail to generate publickey pair hex slice") + ErrInvalidPrivateKey = errors.New("Invalid private key") + ErrInvalidOTAMixSet = errors.New("Invalid OTA mix set") + ErrInvalidOTAAddr = errors.New("Invalid OTA address") + ErrReqTooManyOTAMix = errors.New("Require too many OTA mix address") + ErrInvalidOTAMixNum = errors.New("Invalid required OTA mix address number") + ErrInvalidInput = errors.New("Invalid input") +) + // PublicEthereumAPI provides an API to access Ethereum related information. // It offers only methods that operate on public data that is freely available to anyone. type PublicEthereumAPI struct { @@ -1822,3 +1834,41 @@ func (s *PublicTransactionPoolAPI) GetUseAddress(ctx context.Context, a common.A return hexutil.Encode(useAddr[:]), nil } + +// GenerateOneTimeAddress returns corresponding One-Time-Address for a given UseAddress +func (s *PublicTransactionPoolAPI) GenerateOneTimeAddress(ctx context.Context, uAddr string) (string, error) { + strlen := len(uAddr) + if strlen != (common.UAddressLength<<1)+2 { + return "", ErrInvalidUAddress + } + + PKBytesSlice, err := hexutil.Decode(uAddr) + if err != nil { + return "", err + } + + PK1, PK2, err := keystore.GeneratePKPairFromUAddress(PKBytesSlice) + if err != nil { + return "", ErrFailToGeneratePKPairFromUAddress + } + + PKPairSlice := hexutil.PKPair2HexSlice(PK1, PK2) + + SKOTA, err := crypto.GenerateOneTimeKey(PKPairSlice[0], PKPairSlice[1], PKPairSlice[2], PKPairSlice[3]) + if err != nil { + return "", err + } + + otaStr := strings.Replace(strings.Join(SKOTA, ""), "0x", "", -1) + raw, err := hexutil.Decode("0x" + otaStr) + if err != nil { + return "", err + } + + rawUanAddr, err := keystore.UaddrFromUncompressedRawBytes(raw) + if err != nil || rawUanAddr == nil { + return "", err + } + + return hexutil.Encode(rawUanAddr[:]), nil +}