From a82626fb9d436daac5ce36e63a98c70674b608a5 Mon Sep 17 00:00:00 2001 From: simlecode <69969590+simlecode@users.noreply.github.com> Date: Wed, 28 Dec 2022 16:26:06 +0800 Subject: [PATCH] feat: support delegated address --- Makefile | 1 - cli/wallet.go | 3 +- crypto/delegated.go | 122 ++++++++++++++++++++++++++++++++++++ crypto/key.go | 10 +++ crypto/key_test.go | 28 +++++++++ example/main.go | 5 +- filemgr/fs_test.go | 3 +- filemgr/jwt.go | 3 +- integration_test/builder.go | 3 +- 9 files changed, 166 insertions(+), 12 deletions(-) create mode 100644 crypto/delegated.go diff --git a/Makefile b/Makefile index 7e1022f..89287cd 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,6 @@ show-env: @echo '-------------------------------------------------' lint: - gofmt -s -w ./ golangci-lint run clean: diff --git a/cli/wallet.go b/cli/wallet.go index c9a8fe7..18fccd5 100644 --- a/cli/wallet.go +++ b/cli/wallet.go @@ -7,7 +7,6 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" "os" "strings" @@ -233,7 +232,7 @@ var walletImport = &cli.Command{ inpdata = indata } else { - fdata, err := ioutil.ReadFile(cctx.Args().First()) + fdata, err := os.ReadFile(cctx.Args().First()) if err != nil { if strings.Contains(err.Error(), "no such file or directory") { return errors.New("input whether it is a file, if not, exec `./venus-wallet import` and then `enter` ") diff --git a/crypto/delegated.go b/crypto/delegated.go new file mode 100644 index 0000000..2cd4eda --- /dev/null +++ b/crypto/delegated.go @@ -0,0 +1,122 @@ +package crypto + +import ( + "fmt" + + "golang.org/x/crypto/sha3" + + "github.com/filecoin-project/go-address" + gocrypto "github.com/filecoin-project/go-crypto" + "github.com/filecoin-project/go-state-types/builtin" + "github.com/filecoin-project/go-state-types/crypto" + "github.com/filecoin-project/venus/venus-shared/types" +) + +type delegatedPrivateKey struct { + key []byte +} + +func newDelegatedKeyFromData(data []byte) PrivateKey { + return &delegatedPrivateKey{ + key: data, + } +} + +func genDelegatedPrivateKey() (PrivateKey, error) { + prv, err := gocrypto.GenerateKey() + if err != nil { + return nil, err + } + p := &delegatedPrivateKey{ + key: prv, + } + return p, nil +} + +func (p *delegatedPrivateKey) Public() []byte { + return gocrypto.PublicKey(p.key) +} + +func (p *delegatedPrivateKey) Sign(msg []byte) (*crypto.Signature, error) { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(msg) + hashSum := hasher.Sum(nil) + sig, err := gocrypto.Sign(p.key, hashSum) + if err != nil { + return nil, err + } + + return &crypto.Signature{ + Type: p.Type(), + Data: sig, + }, nil +} + +func (p *delegatedPrivateKey) Bytes() []byte { + return p.key +} + +func (p *delegatedPrivateKey) Address() (address.Address, error) { + pubKey := p.Public() + // Transitory Delegated signature verification as per FIP-0055 + ethAddr, err := types.EthAddressFromPubKey(pubKey) + if err != nil { + return address.Undef, fmt.Errorf("failed to calculate Eth address from public key: %w", err) + } + ea, err := types.CastEthAddress(ethAddr) + if err != nil { + return address.Undef, fmt.Errorf("failed to create ethereum address from bytes: %w", err) + } + + return ea.ToFilecoinAddress() +} + +func (p *delegatedPrivateKey) Type() types.SigType { + return types.SigTypeDelegated +} + +func (p *delegatedPrivateKey) KeyType() types.KeyType { + return types.KTDelegated +} + +func (p *delegatedPrivateKey) ToKeyInfo() *types.KeyInfo { + return &types.KeyInfo{ + PrivateKey: p.Bytes(), + Type: types.KTSecp256k1, + } +} + +func delegatedVerify(sig []byte, a address.Address, msg []byte) error { + hasher := sha3.NewLegacyKeccak256() + hasher.Write(msg) + hash := hasher.Sum(nil) + + pubk, err := gocrypto.EcRecover(hash, sig) + if err != nil { + return err + } + + // if we get an uncompressed public key (that's what we get from the library, + // but putting this check here for defensiveness), strip the prefix + if pubk[0] == 0x04 { + pubk = pubk[1:] + } + + hasher.Reset() + hasher.Write(pubk) + addrHash := hasher.Sum(nil) + + // The address hash will not start with [12]byte{0xff}, so we don't have to use + // EthAddr.ToFilecoinAddress() to handle the case with an id address + // Also, importing ethtypes here will cause circulating import + maybeaddr, err := address.NewDelegatedAddress(builtin.EthereumAddressManagerActorID, addrHash[12:]) + if err != nil { + return err + } + + if maybeaddr != a { + return fmt.Errorf("signature did not match") + } + + return nil +} diff --git a/crypto/key.go b/crypto/key.go index a289bf1..fe433d8 100644 --- a/crypto/key.go +++ b/crypto/key.go @@ -38,6 +38,8 @@ func Verify(sig *crypto.Signature, addr address.Address, msg []byte) error { return secpVerify(sig.Data, addr, msg) case types.SigTypeBLS: return blsVerify(sig.Data, addr, msg) + case types.SigTypeDelegated: + return delegatedVerify(sig.Data, addr, msg) default: return fmt.Errorf("cannot verify signature of unsupported type: %v", sig.Type) } @@ -49,6 +51,8 @@ func GeneratePrivateKey(st types.SigType) (PrivateKey, error) { return genSecpPrivateKey() case types.SigTypeBLS: return genBlsPrivate() + case types.SigTypeDelegated: + return genDelegatedPrivateKey() default: return nil, fmt.Errorf("invalid signature type: %d", st) } @@ -60,6 +64,8 @@ func NewKeyFromKeyInfo(ki *types.KeyInfo) (PrivateKey, error) { return newBlsKeyFromData(ki.PrivateKey) case types.KTSecp256k1: return newSecpKeyFromData(ki.PrivateKey), nil + case types.KTDelegated: + return newDelegatedKeyFromData(ki.PrivateKey), nil default: return nil, fmt.Errorf("invalid key type: %s", ki.Type) } @@ -71,6 +77,8 @@ func NewKeyFromData2(kt types.KeyType, prv []byte) (PrivateKey, error) { return newBlsKeyFromData(prv) case types.KTSecp256k1: return newSecpKeyFromData(prv), nil + case types.KTDelegated: + return newDelegatedKeyFromData(prv), nil default: return nil, fmt.Errorf("invalid key type: %s", kt) } @@ -82,6 +90,8 @@ func NewKeyFromData(st types.SigType, prv []byte) (PrivateKey, error) { return newSecpKeyFromData(prv), nil case types.SigTypeBLS: return newBlsKeyFromData(prv) + case types.SigTypeDelegated: + return newDelegatedKeyFromData(prv), nil default: return nil, fmt.Errorf("invalid signature type: %d", st) } diff --git a/crypto/key_test.go b/crypto/key_test.go index a43af1a..3d41052 100644 --- a/crypto/key_test.go +++ b/crypto/key_test.go @@ -69,3 +69,31 @@ func TestBLSPrivateKey(t *testing.T) { } assert.Equal(t, fmt.Sprintf("%x", signature.Data), "828e485cf906ae5deda33ec3e16b2a5ac761ab5a6109f481134a4dd40d7b5c3b03d187f614c7dd85d142ff50b902bdf50923f58b5bb741b4a522bf17f8efe5cf3f8f51715cd31a765eb5e5d76889102752d2cc2efa4287829cacb4de2be07cf7") } + +func TestDelegatedPrivateKey(t *testing.T) { + k := "33ff38fa8c1c53c3ef1b2f811cee9ce4f7ec2ce90b16ceeb9675dc6aa3d04821" + key, err := hex.DecodeString(k) + if err != nil { + t.Fatal(err) + } + prv, err := NewKeyFromData2(types.KTDelegated, key) + if err != nil { + t.Fatalf("load private key from bytes err:%s", err) + } + + pub := prv.Public() + assert.Equal(t, fmt.Sprintf("%x", pub), "04f047588679644c440d8f7878fe29730e0cdc8c21b32ab8be5c8c01e1bf5d25e09609fff9c037fcd5cd6ac090e44a438c3d13a31a39a96a4b412831e4a3c83b0e") + + addr, err := prv.Address() + if err != nil { + t.Fatalf("private key parse address error:%s", err) + } + assert.Equal(t, addr.String(), "t410fnz6o7a4owcgw33z2zvsxtuf64aoxlmcn4dh5hzy") + + signData := []byte("hello filecoin") + signature, err := prv.Sign(signData) + if err != nil { + t.Fatalf("private key sign data err:%s", err) + } + assert.Equal(t, fmt.Sprintf("%x", signature.Data), "e8f6ad5958dde03013e45fc77f71238cd0013af0d05ec5569007f70ffa7d2453007e7a581dcdf6d9f8e89846de210b74b188d8e1d2629f2d351e9e92f08be29600") +} diff --git a/example/main.go b/example/main.go index 08bc149..1abc2ff 100644 --- a/example/main.go +++ b/example/main.go @@ -4,7 +4,6 @@ import ( "context" "crypto/sha256" "fmt" - "io/ioutil" "log" "os" "os/exec" @@ -79,12 +78,12 @@ func main() { if err != nil { log.Fatal(err) } - tb, err := ioutil.ReadFile(path.Join(dir, "example", "remote-token.tmp")) + tb, err := os.ReadFile(path.Join(dir, "example", "remote-token.tmp")) if err != nil { log.Fatal(err) } token := strings.TrimSpace(string(tb)) - pb, err := ioutil.ReadFile(path.Join(dir, "example", "pid.tmp")) + pb, err := os.ReadFile(path.Join(dir, "example", "pid.tmp")) if err != nil { log.Fatal(err) } diff --git a/filemgr/fs_test.go b/filemgr/fs_test.go index 613209d..f14b108 100644 --- a/filemgr/fs_test.go +++ b/filemgr/fs_test.go @@ -2,7 +2,6 @@ package filemgr import ( - "io/ioutil" "os" "testing" @@ -12,7 +11,7 @@ import ( func TestNewFS(t *testing.T) { // stm: @VENUSWALLET_FILEMGR_FS_NEW_001 - fsPath, err := ioutil.TempDir("", "venus-repo-") + fsPath, err := os.MkdirTemp("", "venus-repo-") defer os.RemoveAll(fsPath) if err != nil { t.Fatal(err) diff --git a/filemgr/jwt.go b/filemgr/jwt.go index 0477520..c4b3993 100644 --- a/filemgr/jwt.go +++ b/filemgr/jwt.go @@ -4,7 +4,6 @@ import ( "crypto/rand" "encoding/hex" "io" - "io/ioutil" "github.com/filecoin-project/venus-wallet/config" "github.com/filecoin-project/venus/venus-shared/api/permission" @@ -22,7 +21,7 @@ type jwtSecret struct { // Random generation of secret keys func randSecret() (*jwtSecret, error) { - sk, err := ioutil.ReadAll(io.LimitReader(rand.Reader, 32)) + sk, err := io.ReadAll(io.LimitReader(rand.Reader, 32)) if err != nil { return nil, err } diff --git a/integration_test/builder.go b/integration_test/builder.go index 3a04046..0150fbd 100644 --- a/integration_test/builder.go +++ b/integration_test/builder.go @@ -3,7 +3,6 @@ package integration import ( "context" "fmt" - "io/ioutil" "os" "strings" "syscall" @@ -104,7 +103,7 @@ func (inst *WalletInst) StopAndWait() error { } func NewWalletInst() (*WalletInst, error) { - dir, err := ioutil.TempDir("", "venus_wallet_") + dir, err := os.MkdirTemp("", "venus_wallet_") if err != nil { return nil, err }