Merge pull request #74 from filecoin-project/feat/payment-channels

implement initial payment channel actor
This commit is contained in:
Whyrusleeping 2019-07-25 13:49:52 -07:00 committed by GitHub
commit 663cdbe167
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 402 additions and 101 deletions

View File

@ -87,7 +87,7 @@ type FullNode interface {
WalletNew(context.Context, string) (address.Address, error) WalletNew(context.Context, string) (address.Address, error)
WalletList(context.Context) ([]address.Address, error) WalletList(context.Context) ([]address.Address, error)
WalletBalance(context.Context, address.Address) (types.BigInt, error) WalletBalance(context.Context, address.Address) (types.BigInt, error)
WalletSign(context.Context, address.Address, []byte) (*chain.Signature, error) WalletSign(context.Context, address.Address, []byte) (*types.Signature, error)
WalletDefaultAddress(context.Context) (address.Address, error) WalletDefaultAddress(context.Context) (address.Address, error)
// Really not sure where this belongs. It could go on the wallet, or the message pool, or the chain... // Really not sure where this belongs. It could go on the wallet, or the message pool, or the chain...

View File

@ -49,7 +49,7 @@ type FullNodeStruct struct {
WalletNew func(context.Context, string) (address.Address, error) `perm:"write"` WalletNew func(context.Context, string) (address.Address, error) `perm:"write"`
WalletList func(context.Context) ([]address.Address, error) `perm:"write"` WalletList func(context.Context) ([]address.Address, error) `perm:"write"`
WalletBalance func(context.Context, address.Address) (types.BigInt, error) `perm:"read"` WalletBalance func(context.Context, address.Address) (types.BigInt, error) `perm:"read"`
WalletSign func(context.Context, address.Address, []byte) (*chain.Signature, error) `perm:"sign"` WalletSign func(context.Context, address.Address, []byte) (*types.Signature, error) `perm:"sign"`
WalletDefaultAddress func(context.Context) (address.Address, error) `perm:"write"` WalletDefaultAddress func(context.Context) (address.Address, error) `perm:"write"`
MpoolGetNonce func(context.Context, address.Address) (uint64, error) `perm:"read"` MpoolGetNonce func(context.Context, address.Address) (uint64, error) `perm:"read"`
@ -147,7 +147,7 @@ func (c *FullNodeStruct) WalletBalance(ctx context.Context, a address.Address) (
return c.Internal.WalletBalance(ctx, a) return c.Internal.WalletBalance(ctx, a)
} }
func (c *FullNodeStruct) WalletSign(ctx context.Context, k address.Address, msg []byte) (*chain.Signature, error) { func (c *FullNodeStruct) WalletSign(ctx context.Context, k address.Address, msg []byte) (*types.Signature, error) {
return c.Internal.WalletSign(ctx, k, msg) return c.Internal.WalletSign(ctx, k, msg)
} }

281
chain/actors/actor_paych.go Normal file
View File

@ -0,0 +1,281 @@
package actors
import (
"bytes"
"github.com/filecoin-project/go-lotus/chain/actors/aerrors"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/chain/types"
cbor "github.com/ipfs/go-ipld-cbor"
)
const ChannelClosingDelay = 6 * 60 * 2 // six hours
func init() {
cbor.RegisterCborType(PaymentChannelActorState{})
cbor.RegisterCborType(PCAConstructorParams{})
cbor.RegisterCborType(SignedVoucher{})
cbor.RegisterCborType(Merge{})
cbor.RegisterCborType(LaneState{})
cbor.RegisterCborType(UpdateChannelState{})
}
type PaymentChannelActor struct{}
type LaneState struct {
Closed bool
Redeemed types.BigInt
Nonce uint64
}
type PaymentChannelActorState struct {
From address.Address
To address.Address
ChannelTotal types.BigInt
ToSend types.BigInt
ClosingAt uint64
MinCloseHeight uint64
LaneStates map[uint64]*LaneState
VerifActor address.Address
VerifMethod uint64
}
func (pca PaymentChannelActor) Exports() []interface{} {
return []interface{}{
0: pca.Constructor,
1: pca.UpdateChannelState,
2: pca.Close,
3: pca.Collect,
}
}
type PCAConstructorParams struct {
To address.Address
VerifActor address.Address
VerifMethod uint64
}
func (pca PaymentChannelActor) Constructor(act *types.Actor, vmctx types.VMContext, params *PCAConstructorParams) ([]byte, ActorError) {
var self PaymentChannelActorState
self.From = vmctx.Origin()
self.To = params.To
self.VerifActor = params.VerifActor
self.VerifMethod = params.VerifMethod
storage := vmctx.Storage()
c, err := storage.Put(self)
if err != nil {
return nil, err
}
if err := storage.Commit(EmptyCBOR, c); err != nil {
return nil, err
}
return nil, nil
}
type SignedVoucher struct {
TimeLock uint64
SecretPreimage []byte
Extra []byte
Lane uint64
Nonce uint64
Amount types.BigInt
MinCloseHeight uint64
Merges []Merge
Signature types.Signature
}
type Merge struct {
Lane uint64
Nonce uint64
}
type UpdateChannelState struct {
Sv SignedVoucher
Secret []byte
Proof []byte
}
func hash(b []byte) []byte {
panic("blake 2b hash pls")
}
func (pca PaymentChannelActor) UpdateChannelState(act *types.Actor, vmctx types.VMContext, params *UpdateChannelState) ([]byte, ActorError) {
var self PaymentChannelActorState
oldstate := vmctx.Storage().GetHead()
storage := vmctx.Storage()
if err := storage.Get(oldstate, &self); err != nil {
return nil, err
}
sv := params.Sv
if err := vmctx.VerifySignature(sv.Signature, self.From); err != nil {
return nil, err
}
if vmctx.BlockHeight() < sv.TimeLock {
return nil, aerrors.New(2, "cannot use this voucher yet!")
}
if sv.SecretPreimage != nil {
if !bytes.Equal(hash(params.Secret), sv.SecretPreimage) {
return nil, aerrors.New(3, "Incorrect secret!")
}
}
if sv.Extra != nil {
if self.VerifActor == address.Undef {
return nil, aerrors.New(4, "no verifActor for extra data")
}
encoded, err := SerializeParams([]interface{}{sv.Extra, params.Proof})
if err != nil {
return nil, err
}
_, err = vmctx.Send(self.VerifActor, self.VerifMethod, types.NewInt(0), encoded)
if err != nil {
return nil, aerrors.New(5, "spend voucher verification failed")
}
}
ls := self.LaneStates[sv.Lane]
if ls.Closed {
return nil, aerrors.New(6, "cannot redeem a voucher on a closed lane")
}
if ls.Nonce > sv.Nonce {
return nil, aerrors.New(7, "voucher has an outdated nonce, cannot redeem")
}
mergeValue := types.NewInt(0)
for _, merge := range sv.Merges {
if merge.Lane == sv.Lane {
return nil, aerrors.New(8, "voucher cannot merge its own lane")
}
ols := self.LaneStates[merge.Lane]
if ols.Nonce >= merge.Nonce {
return nil, aerrors.New(9, "merge in voucher has outdated nonce, cannot redeem")
}
mergeValue = types.BigAdd(mergeValue, ols.Redeemed)
ols.Nonce = merge.Nonce
}
ls.Nonce = sv.Nonce
balanceDelta := types.BigSub(sv.Amount, types.BigAdd(mergeValue, ls.Redeemed))
ls.Redeemed = sv.Amount
newSendBalance := types.BigAdd(self.ToSend, balanceDelta)
if types.BigCmp(newSendBalance, types.NewInt(0)) < 0 {
// TODO: is this impossible?
return nil, aerrors.New(10, "voucher would leave channel balance negative")
}
if types.BigCmp(newSendBalance, self.ChannelTotal) > 0 {
return nil, aerrors.New(11, "not enough funds in channel to cover voucher")
}
self.ToSend = newSendBalance
if sv.MinCloseHeight != 0 {
if self.ClosingAt != 0 && self.ClosingAt < sv.MinCloseHeight {
self.ClosingAt = sv.MinCloseHeight
}
if self.MinCloseHeight < sv.MinCloseHeight {
self.MinCloseHeight = sv.MinCloseHeight
}
}
ncid, err := storage.Put(self)
if err != nil {
return nil, err
}
if err := storage.Commit(oldstate, ncid); err != nil {
return nil, err
}
return nil, nil
}
func (pca PaymentChannelActor) Close(act *types.Actor, vmctx types.VMContext, params struct{}) ([]byte, aerrors.ActorError) {
var self PaymentChannelActorState
storage := vmctx.Storage()
oldstate := storage.GetHead()
if err := storage.Get(oldstate, &self); err != nil {
return nil, err
}
if vmctx.Message().From != self.From && vmctx.Message().From != self.To {
return nil, aerrors.New(1, "not authorized to close channel")
}
if self.ClosingAt != 0 {
return nil, aerrors.New(2, "channel already closing")
}
self.ClosingAt = vmctx.BlockHeight() + ChannelClosingDelay
if self.ClosingAt < self.MinCloseHeight {
self.ClosingAt = self.MinCloseHeight
}
ncid, err := storage.Put(self)
if err != nil {
return nil, err
}
if err := storage.Commit(oldstate, ncid); err != nil {
return nil, err
}
return nil, nil
}
func (pca PaymentChannelActor) Collect(act *types.Actor, vmctx types.VMContext, params struct{}) ([]byte, aerrors.ActorError) {
var self PaymentChannelActorState
storage := vmctx.Storage()
oldstate := storage.GetHead()
if err := storage.Get(oldstate, &self); err != nil {
return nil, err
}
if self.ClosingAt == 0 {
return nil, aerrors.New(1, "payment channel not closing or closed")
}
if vmctx.BlockHeight() < self.ClosingAt {
return nil, aerrors.New(2, "payment channel not closed yet")
}
_, err := vmctx.Send(self.From, 0, types.BigSub(self.ChannelTotal, self.ToSend), nil)
if err != nil {
return nil, err
}
_, err = vmctx.Send(self.To, 0, self.ToSend, nil)
if err != nil {
return nil, err
}
self.ChannelTotal = types.NewInt(0)
self.ToSend = types.NewInt(0)
ncid, err := storage.Put(self)
if err != nil {
return nil, err
}
if err := storage.Commit(oldstate, ncid); err != nil {
return nil, err
}
return nil, nil
}

View File

@ -161,7 +161,7 @@ func SetupStorageMarketActor(bs bstore.Blockstore) (*types.Actor, error) {
func MakeGenesisBlock(bs bstore.Blockstore, w *Wallet) (*GenesisBootstrap, error) { func MakeGenesisBlock(bs bstore.Blockstore, w *Wallet) (*GenesisBootstrap, error) {
fmt.Println("at end of make Genesis block") fmt.Println("at end of make Genesis block")
minerAddr, err := w.GenerateKey(KTSecp256k1) minerAddr, err := w.GenerateKey(types.KTSecp256k1)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -46,7 +46,7 @@ func MinerCreateBlock(cs *ChainStore, miner address.Address, parents *TipSet, ti
fmt.Printf("adding %d messages to block...", len(msgs)) fmt.Printf("adding %d messages to block...", len(msgs))
var msgCids []cid.Cid var msgCids []cid.Cid
var blsSigs []Signature var blsSigs []types.Signature
var receipts []interface{} var receipts []interface{}
for _, msg := range msgs { for _, msg := range msgs {
if msg.Signature.TypeCode() == 2 { if msg.Signature.TypeCode() == 2 {
@ -108,7 +108,7 @@ func MinerCreateBlock(cs *ChainStore, miner address.Address, parents *TipSet, ti
return fullBlock, nil return fullBlock, nil
} }
func aggregateSignatures(sigs []Signature) (Signature, error) { func aggregateSignatures(sigs []types.Signature) (types.Signature, error) {
var blsSigs []bls.Signature var blsSigs []bls.Signature
for _, s := range sigs { for _, s := range sigs {
var bsig bls.Signature var bsig bls.Signature
@ -117,8 +117,8 @@ func aggregateSignatures(sigs []Signature) (Signature, error) {
} }
aggSig := bls.Aggregate(blsSigs) aggSig := bls.Aggregate(blsSigs)
return Signature{ return types.Signature{
Type: KTBLS, Type: types.KTBLS,
Data: aggSig[:], Data: aggSig[:],
}, nil }, nil
} }

View File

@ -1,7 +1,6 @@
package chain package chain
import ( import (
"encoding/binary"
"encoding/json" "encoding/json"
"fmt" "fmt"
@ -38,7 +37,7 @@ func init() {
return SignedMessage{}, fmt.Errorf("signature in signed message was not bytes") return SignedMessage{}, fmt.Errorf("signature in signed message was not bytes")
} }
sig, err := SignatureFromBytes(sigb) sig, err := types.SignatureFromBytes(sigb)
if err != nil { if err != nil {
return SignedMessage{}, err return SignedMessage{}, err
} }
@ -49,18 +48,6 @@ func init() {
}, nil }, nil
})). })).
Complete()) Complete())
cbor.RegisterCborType(atlas.BuildEntry(Signature{}).Transform().
TransformMarshal(atlas.MakeMarshalTransformFunc(
func(s Signature) ([]byte, error) {
buf := make([]byte, 4)
n := binary.PutUvarint(buf, uint64(s.TypeCode()))
return append(buf[:n], s.Data...), nil
})).
TransformUnmarshal(atlas.MakeUnmarshalTransformFunc(
func(x []byte) (Signature, error) {
return SignatureFromBytes(x)
})).
Complete())
cbor.RegisterCborType(atlas.BuildEntry(BlockHeader{}).UseTag(43).Transform(). cbor.RegisterCborType(atlas.BuildEntry(BlockHeader{}).UseTag(43).Transform().
TransformMarshal(atlas.MakeMarshalTransformFunc( TransformMarshal(atlas.MakeMarshalTransformFunc(
func(blk BlockHeader) ([]interface{}, error) { func(blk BlockHeader) ([]interface{}, error) {
@ -140,7 +127,7 @@ type BlockHeader struct {
Messages cid.Cid Messages cid.Cid
BLSAggregate Signature BLSAggregate types.Signature
MessageReceipts cid.Cid MessageReceipts cid.Cid
} }
@ -208,7 +195,7 @@ func (m *SignedMessage) Cid() cid.Cid {
type SignedMessage struct { type SignedMessage struct {
Message types.Message Message types.Message
Signature Signature Signature types.Signature
} }
func DecodeSignedMessage(data []byte) (*SignedMessage, error) { func DecodeSignedMessage(data []byte) (*SignedMessage, error) {

92
chain/types/signature.go Normal file
View File

@ -0,0 +1,92 @@
package types
import (
"encoding/binary"
"fmt"
"github.com/filecoin-project/go-lotus/chain/address"
"github.com/filecoin-project/go-lotus/lib/crypto"
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/minio/blake2b-simd"
"github.com/polydawn/refmt/obj/atlas"
)
const (
KTSecp256k1 = "secp256k1"
KTBLS = "bls"
)
func init() {
cbor.RegisterCborType(atlas.BuildEntry(Signature{}).Transform().
TransformMarshal(atlas.MakeMarshalTransformFunc(
func(s Signature) ([]byte, error) {
buf := make([]byte, 4)
n := binary.PutUvarint(buf, uint64(s.TypeCode()))
return append(buf[:n], s.Data...), nil
})).
TransformUnmarshal(atlas.MakeUnmarshalTransformFunc(
func(x []byte) (Signature, error) {
return SignatureFromBytes(x)
})).
Complete())
}
type Signature struct {
Type string
Data []byte
}
func SignatureFromBytes(x []byte) (Signature, error) {
val, nr := binary.Uvarint(x)
if nr != 1 {
return Signature{}, fmt.Errorf("signatures with type field longer than one byte are invalid")
}
var ts string
switch val {
case 1:
ts = KTSecp256k1
default:
return Signature{}, fmt.Errorf("unsupported signature type: %d", val)
}
return Signature{
Type: ts,
Data: x[1:],
}, nil
}
func (s *Signature) Verify(addr address.Address, msg []byte) error {
b2sum := blake2b.Sum256(msg)
switch s.Type {
case KTSecp256k1:
pubk, err := crypto.EcRecover(b2sum[:], s.Data)
if err != nil {
return err
}
maybeaddr, err := address.NewSecp256k1Address(pubk)
if err != nil {
return err
}
if addr != maybeaddr {
return fmt.Errorf("signature did not match")
}
return nil
default:
return fmt.Errorf("cannot verify signature of unsupported type: %s", s.Type)
}
}
func (s *Signature) TypeCode() int {
switch s.Type {
case KTSecp256k1:
return 1
case KTBLS:
return 2
default:
panic("unsupported signature type")
}
}

View File

@ -32,4 +32,5 @@ type VMContext interface {
GasUsed() BigInt GasUsed() BigInt
Storage() Storage Storage() Storage
StateTree() (StateTree, aerrors.ActorError) StateTree() (StateTree, aerrors.ActorError)
VerifySignature(sig Signature, from address.Address) aerrors.ActorError
} }

View File

@ -129,6 +129,10 @@ func (vmc *VMContext) StateTree() (types.StateTree, aerrors.ActorError) {
return vmc.state, nil return vmc.state, nil
} }
func (vmctx *VMContext) VerifySignature(sig types.Signature, act address.Address) aerrors.ActorError {
panic("NYI")
}
func (vm *VM) makeVMContext(sroot cid.Cid, origin address.Address, msg *types.Message) *VMContext { func (vm *VM) makeVMContext(sroot cid.Cid, origin address.Address, msg *types.Message) *VMContext {
cst := hamt.CSTFromBstore(vm.cs.bs) cst := hamt.CSTFromBstore(vm.cs.bs)

View File

@ -1,8 +1,6 @@
package chain package chain
import ( import (
"encoding/binary"
"fmt"
"sort" "sort"
"strings" "strings"
@ -17,9 +15,6 @@ import (
const ( const (
KNamePrefix = "wallet-" KNamePrefix = "wallet-"
KTSecp256k1 = "secp256k1"
KTBLS = "bls"
) )
type Wallet struct { type Wallet struct {
@ -36,91 +31,31 @@ func NewWallet(keystore types.KeyStore) (*Wallet, error) {
return w, nil return w, nil
} }
type Signature struct { func (w *Wallet) Sign(addr address.Address, msg []byte) (*types.Signature, error) {
Type string
Data []byte
}
func SignatureFromBytes(x []byte) (Signature, error) {
val, nr := binary.Uvarint(x)
if nr != 1 {
return Signature{}, fmt.Errorf("signatures with type field longer than one byte are invalid")
}
var ts string
switch val {
case 1:
ts = KTSecp256k1
default:
return Signature{}, fmt.Errorf("unsupported signature type: %d", val)
}
return Signature{
Type: ts,
Data: x[1:],
}, nil
}
func (s *Signature) Verify(addr address.Address, msg []byte) error {
b2sum := blake2b.Sum256(msg)
switch s.Type {
case KTSecp256k1:
pubk, err := crypto.EcRecover(b2sum[:], s.Data)
if err != nil {
return err
}
maybeaddr, err := address.NewSecp256k1Address(pubk)
if err != nil {
return err
}
if addr != maybeaddr {
return fmt.Errorf("signature did not match")
}
return nil
default:
return fmt.Errorf("cannot verify signature of unsupported type: %s", s.Type)
}
}
func (s *Signature) TypeCode() int {
switch s.Type {
case KTSecp256k1:
return 1
case KTBLS:
return 2
default:
panic("unsupported signature type")
}
}
func (w *Wallet) Sign(addr address.Address, msg []byte) (*Signature, error) {
ki, err := w.findKey(addr) ki, err := w.findKey(addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
switch ki.Type { switch ki.Type {
case KTSecp256k1: case types.KTSecp256k1:
b2sum := blake2b.Sum256(msg) b2sum := blake2b.Sum256(msg)
sig, err := crypto.Sign(ki.PrivateKey, b2sum[:]) sig, err := crypto.Sign(ki.PrivateKey, b2sum[:])
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Signature{ return &types.Signature{
Type: KTSecp256k1, Type: types.KTSecp256k1,
Data: sig, Data: sig,
}, nil }, nil
case KTBLS: case types.KTBLS:
var pk bls.PrivateKey var pk bls.PrivateKey
copy(pk[:], ki.PrivateKey) copy(pk[:], ki.PrivateKey)
sig := bls.PrivateKeySign(pk, msg) sig := bls.PrivateKeySign(pk, msg)
return &Signature{ return &types.Signature{
Type: KTBLS, Type: types.KTBLS,
Data: sig[:], Data: sig[:],
}, nil }, nil
@ -180,7 +115,7 @@ func (w *Wallet) ListAddrs() ([]address.Address, error) {
func (w *Wallet) GenerateKey(typ string) (address.Address, error) { func (w *Wallet) GenerateKey(typ string) (address.Address, error) {
var k *Key var k *Key
switch typ { switch typ {
case KTSecp256k1: case types.KTSecp256k1:
priv, err := crypto.GenerateKey() priv, err := crypto.GenerateKey()
if err != nil { if err != nil {
return address.Undef, err return address.Undef, err
@ -194,7 +129,7 @@ func (w *Wallet) GenerateKey(typ string) (address.Address, error) {
if err != nil { if err != nil {
return address.Undef, err return address.Undef, err
} }
case KTBLS: case types.KTBLS:
priv := bls.PrivateKeyGenerate() priv := bls.PrivateKeyGenerate()
ki := types.KeyInfo{ ki := types.KeyInfo{
Type: typ, Type: typ,
@ -231,7 +166,7 @@ func NewKey(keyinfo types.KeyInfo) (*Key, error) {
} }
switch k.Type { switch k.Type {
case KTSecp256k1: case types.KTSecp256k1:
k.PublicKey = crypto.PublicKey(k.PrivateKey) k.PublicKey = crypto.PublicKey(k.PrivateKey)
var err error var err error
@ -240,7 +175,7 @@ func NewKey(keyinfo types.KeyInfo) (*Key, error) {
return nil, xerrors.Errorf("converting Secp256k1 to address: %w", err) return nil, xerrors.Errorf("converting Secp256k1 to address: %w", err)
} }
case KTBLS: case types.KTBLS:
var pk bls.PrivateKey var pk bls.PrivateKey
copy(pk[:], k.PrivateKey) copy(pk[:], k.PrivateKey)
pub := bls.PrivateKeyPublicKey(pk) pub := bls.PrivateKeyPublicKey(pk)

1
go.mod
View File

@ -69,6 +69,7 @@ require (
go4.org v0.0.0-20190313082347-94abd6928b1d // indirect go4.org v0.0.0-20190313082347-94abd6928b1d // indirect
golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect golang.org/x/sync v0.0.0-20190423024810-112230192c58 // indirect
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7
google.golang.org/appengine v1.4.0 // indirect
gopkg.in/urfave/cli.v2 v2.0.0-20180128182452-d3ae77c26ac8 gopkg.in/urfave/cli.v2 v2.0.0-20180128182452-d3ae77c26ac8
launchpad.net/gocheck v0.0.0-20140225173054-000000000087 // indirect launchpad.net/gocheck v0.0.0-20140225173054-000000000087 // indirect
) )

View File

@ -123,7 +123,7 @@ func (a *FullNodeAPI) WalletBalance(ctx context.Context, addr address.Address) (
return a.Chain.GetBalance(addr) return a.Chain.GetBalance(addr)
} }
func (a *FullNodeAPI) WalletSign(ctx context.Context, k address.Address, msg []byte) (*chain.Signature, error) { func (a *FullNodeAPI) WalletSign(ctx context.Context, k address.Address, msg []byte) (*types.Signature, error) {
return a.Wallet.Sign(k, msg) return a.Wallet.Sign(k, msg)
} }