Skip to content

Commit

Permalink
access ticket example
Browse files Browse the repository at this point in the history
  • Loading branch information
Lodek committed Jul 23, 2024
1 parent 1b7415d commit 06d1f1c
Show file tree
Hide file tree
Showing 10 changed files with 320 additions and 71 deletions.
233 changes: 233 additions & 0 deletions example/access_ticket/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
package main

import (
"context"
"log"
"time"

"github.com/cosmos/cosmos-sdk/codec"
cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptocdc "github.com/cosmos/cosmos-sdk/crypto/codec"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/davecgh/go-spew/spew"
coretypes "github.com/sourcenetwork/acp_core/pkg/types"
"github.com/spf13/cobra"

"github.com/sourcenetwork/sourcehub/sdk"
"github.com/sourcenetwork/sourcehub/x/acp/access_ticket"
acptypes "github.com/sourcenetwork/sourcehub/x/acp/types"
)

const nodeAddrDefault = "tcp://localhost:26657"

const chainIdFlag = "chain-id"
const nodeAddrFlag = "node-addr"

var policy string = `
name: access ticket example
resources:
file:
relations:
owner:
types:
- actor
permissions:
read:
expr: owner
`

func main() {
cmd := cobra.Command{
Use: "access_ticket_demo [validator-key-name]",
Short: "acces_ticket_demo executes a self-contained example of the access ticket flow. Receives name of the validator key to send Txs from",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
name := args[0]
runDemo(
cmd.Flag(chainIdFlag).Value.String(),
cmd.Flag(nodeAddrFlag).Value.String(),
name,
)
},
}
flags := cmd.Flags()
flags.String(chainIdFlag, "sourcehub-dev", "sets the chain-id")
flags.String(nodeAddrFlag, nodeAddrDefault, "sets the address of the node to communicate with")

cmd.Execute()
}

func runDemo(chainId string, nodeAddr string, validatorKeyName string) {
ctx := context.Background()

client, err := sdk.NewClient()
if err != nil {
log.Fatal(err)
}
txBuilder, err := sdk.NewTxBuilder(
sdk.WithSDKClient(client),
sdk.WithChainID(chainId),
)
if err != nil {
log.Fatal(err)
}

txSigner := getSigner(validatorKeyName)

log.Printf("Creating Policy: %v", policy)
policy := createPolicy(ctx, client, &txBuilder, txSigner)

log.Printf("Registering object: file:readme")
record := registerObject(ctx, client, &txBuilder, txSigner, policy.Id)

log.Printf("Evaluating Access Request to read file:readme")
decision := checkAccess(ctx, client, &txBuilder, txSigner, policy.Id, record.OwnerDid, []*acptypes.Operation{
{
Object: coretypes.NewObject("file", "readme"),
Permission: "read",
},
})

log.Printf("Issueing ticket for Access Decision %v", decision.Id)
abciService, err := access_ticket.NewABCIService(nodeAddr)
if err != nil {
log.Fatalf("could not create abci service: %v", err)
}
issuer := access_ticket.NewTicketIssuer(&abciService)
ticket, err := issuer.Issue(ctx, decision.Id, txSigner.GetPrivateKey())
if err != nil {
log.Fatalf("could not issue ticket: %v", err)
}
log.Printf("Access Ticket issued: %v", ticket)

log.Printf("Waiting for next block")
time.Sleep(time.Second * 5)

log.Printf("Verifying Access Ticket")
recoveredTicket, err := access_ticket.UnmarshalAndVerify(ctx, nodeAddr, ticket)
if err != nil {
log.Fatalf("could not verify ticket: %v", err)
}

// remove some fields for cleaner print
recoveredTicket.Signature = nil
recoveredTicket.DecisionProof = nil
log.Printf("Acces Ticket verified: %v", spew.Sdump(recoveredTicket))
}

func getSigner(accAddr string) sdk.TxSigner {
reg := cdctypes.NewInterfaceRegistry()
cryptocdc.RegisterInterfaces(reg)
cdc := codec.NewProtoCodec(reg)
keyring, err := keyring.New("sourcehub", keyring.BackendTest, "~/.sourcehub", nil, cdc)
if err != nil {
log.Fatalf("could not load keyring: %v", err)
}

txSigner, err := sdk.NewTxSignerFromKeyringKey(keyring, accAddr)
if err != nil {
log.Fatalf("could not load keyring: %v", err)
}
return txSigner
}

func createPolicy(ctx context.Context, client *sdk.Client, txBuilder *sdk.TxBuilder, txSigner sdk.TxSigner) *coretypes.Policy {
msgSet := sdk.MsgSet{}
policyMapper := msgSet.WithCreatePolicy(acptypes.NewMsgCreatePolicyNow(txSigner.GetAccAddress(), policy, coretypes.PolicyMarshalingType_SHORT_YAML))
tx, err := txBuilder.Build(ctx, txSigner, &msgSet)
if err != nil {
log.Fatal(err)
}

resp, err := client.BroadcastTx(ctx, tx)
if err != nil {
log.Fatal(err)
}

result, err := client.AwaitTx(ctx, resp.TxHash)
if err != nil {
log.Fatal(err)
}
if result.Error() != nil {
log.Fatalf("Tx failed: %v", result.Error())
}

policyResponse, err := policyMapper.Map(result.TxPayload())
if err != nil {
log.Fatal(err)
}

log.Printf("policy created: %v", policyResponse.Policy.Id)
return policyResponse.Policy
}

func registerObject(ctx context.Context, client *sdk.Client, txBuilder *sdk.TxBuilder, txSigner sdk.TxSigner, policyId string) *coretypes.RelationshipRecord {
msgSet := sdk.MsgSet{}
mapper := msgSet.WithDirectPolicyCmd(
acptypes.NewMsgDirectPolicyCmdNow(
txSigner.GetAccAddress(),
policyId,
acptypes.NewRegisterObjectCmd(coretypes.NewObject("file", "readme")),
),
)
tx, err := txBuilder.Build(ctx, txSigner, &msgSet)
if err != nil {
log.Fatal(err)
}

resp, err := client.BroadcastTx(ctx, tx)
if err != nil {
log.Fatal(err)
}

result, err := client.AwaitTx(ctx, resp.TxHash)
if err != nil {
log.Fatal(err)
}
if result.Error() != nil {
log.Fatalf("Tx failed: %v", result.Error())
}

response, err := mapper.Map(result.TxPayload())
if err != nil {
log.Fatal(err)
}

log.Printf("object registered: %v", response.Result.GetRegisterObjectResult())
return response.Result.GetRegisterObjectResult().Record
}

func checkAccess(ctx context.Context, client *sdk.Client, txBuilder *sdk.TxBuilder, txSigner sdk.TxSigner, policyId string, actorId string, operations []*acptypes.Operation) *acptypes.AccessDecision {
msgSet := sdk.MsgSet{}
mapper := msgSet.WithCheckAccess(
acptypes.NewMsgCheckAccess(txSigner.GetAccAddress(), policyId, &acptypes.AccessRequest{
Operations: operations,
Actor: &coretypes.Actor{Id: actorId},
}),
)
tx, err := txBuilder.Build(ctx, txSigner, &msgSet)
if err != nil {
log.Fatal(err)
}

resp, err := client.BroadcastTx(ctx, tx)
if err != nil {
log.Fatal(err)
}

result, err := client.AwaitTx(ctx, resp.TxHash)
if err != nil {
log.Fatal(err)
}
if result.Error() != nil {
log.Fatalf("Tx failed: %v", result.Error())
}

response, err := mapper.Map(result.TxPayload())
if err != nil {
log.Fatal(err)
}

log.Printf("acces request evaluated: %v", response.Decision)
return response.Decision
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ require (
github.com/cosmos/gogoproto v1.4.12
github.com/cosmos/ibc-go/modules/capability v1.0.0
github.com/cosmos/ibc-go/v8 v8.0.0
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
github.com/go-jose/go-jose/v3 v3.0.1-0.20221117193127-916db76e8214
github.com/golang/protobuf v1.5.4
Expand Down Expand Up @@ -110,7 +111,6 @@ require (
github.com/creachadair/atomicfile v0.3.1 // indirect
github.com/creachadair/tomledit v0.0.24 // indirect
github.com/danieljoos/wincred v1.2.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
Expand Down
2 changes: 1 addition & 1 deletion sdk/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func (b *Client) BroadcastTx(ctx context.Context, tx xauthsigning.Tx) (*sdk.TxRe

response := grpcRes.TxResponse

log.Printf("broadcast tx: %v", grpcRes)
//log.Printf("broadcast tx: %v", grpcRes)
if response.Code != 0 {
return response, fmt.Errorf("tx rejected: codespace %v: code %v: %v", response.Codespace, response.Code, response.RawLog)
}
Expand Down
43 changes: 43 additions & 0 deletions sdk/tx_signer.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/tx/signing"

"github.com/sourcenetwork/sourcehub/types"
)

var secp256k1MsgTypeUrl = cdctypes.MsgTypeURL(&secp256k1.PubKey{})
Expand Down Expand Up @@ -57,6 +59,47 @@ func NewTxSignerFromKeyringKey(keyring keyring.Keyring, name string) (TxSigner,
}, nil
}

// NewTxSignerFromAccountAddress takes a cosmos keyring and an account address
// and returns a TxSigner capable of signing Txs.
// If there are no keys matching the given address, returns an error.
//
// In order to sign Txs, the key must be of type secp256k1, as it's the only supported
// Tx signing key in CosmosSDK.
// See https://docs.cosmos.network/main/learn/beginner/accounts#keys-accounts-addresses-and-signatures
//
// Note: The adapter does not access the private key bytes directly, instead delegating
// the signing to the keyring itself. As such, any attempt to dump the bytes of the priv key
// will cause a panic
func NewTxSignerFromAccountAddress(keyring keyring.Keyring, address string) (TxSigner, error) {
accAddr, err := types.AccAddressFromBech32(address)
if err != nil {
return nil, err
}
record, err := keyring.KeyByAddress(accAddr)
if err != nil {
return nil, err
}
if record == nil {
return nil, fmt.Errorf("key %v not found for keyring", address)
}

if record.PubKey.TypeUrl != secp256k1MsgTypeUrl {
return nil, fmt.Errorf("cannot create signer from key %v: key must be of type secp256k1", address)
}

pubKey := &secp256k1.PubKey{}
err = pubKey.Unmarshal(record.PubKey.Value)
if err != nil {
return nil, fmt.Errorf("could not unmarshal key %v: %v", address, err)
}

return &keyringTxSigner{
keyring: keyring,
keyname: address,
pubkey: pubKey,
}, nil
}

// keyringTxSigner wraps a keyring as a TxSigner
type keyringTxSigner struct {
keyring keyring.Keyring
Expand Down
16 changes: 11 additions & 5 deletions x/acp/access_ticket/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import (
"fmt"

storetypes "cosmossdk.io/store/types"
"github.com/TBD54566975/ssi-sdk/crypto"
"github.com/TBD54566975/ssi-sdk/did/key"
abcitypes "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/crypto/merkle"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"

"github.com/sourcenetwork/sourcehub/x/acp/did"
"github.com/sourcenetwork/sourcehub/x/acp/types"
Expand Down Expand Up @@ -45,15 +48,18 @@ func (s *AccessTicketSpec) SatisfiesRaw(ctx context.Context, ticket *types.Acces
}

didStr := ticket.Decision.Actor
doc, err := s.resolver.Resolve(ctx, didStr)
if err != nil {
return err
}

pkey, err := did.ExtractVerificationKey(doc)
did := key.DIDKey(didStr)
pubBytes, _, keytype, err := did.Decode()
if err != nil {
return err
}
if keytype != crypto.SECP256k1 {
return fmt.Errorf("unsuported key type: expected SECP256k1: got %v", keytype)
}
pkey := &secp256k1.PubKey{
Key: pubBytes,
}

err = s.signer.Verify(pkey, ticket)
if err != nil {
Expand Down
23 changes: 23 additions & 0 deletions x/acp/access_ticket/types.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package access_ticket

import (
"context"
"encoding/base64"
"fmt"
"strings"

"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/sourcenetwork/sourcehub/x/acp/did"
"github.com/sourcenetwork/sourcehub/x/acp/types"
)

Expand Down Expand Up @@ -92,3 +94,24 @@ func (b *keyBuilder) ABCIQueryPath() string {

return "/" + baseapp.QueryPathStore + "/" + types.ModuleName + "/" + iavlQuerySuffix
}

func UnmarshalAndVerify(ctx context.Context, trustedNodeAddr string, ticketStr string) (*types.AccessTicket, error) {
marshaler := Marshaler{}
service, err := NewABCIService(trustedNodeAddr)
if err != nil {
return nil, err
}
ticketSpec := NewAccessTicketSpec(&service, &did.KeyResolver{})

ticket, err := marshaler.Unmarshal(ticketStr)
if err != nil {
return nil, err
}

err = ticketSpec.SatisfiesRaw(ctx, ticket)
if err != nil {
return nil, err
}

return ticket, nil
}
Loading

0 comments on commit 06d1f1c

Please sign in to comment.