Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Embedded key loader #82

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 70 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

### General description:

The goal of iden3auth libraries is to handle authentication messages of communication protocol.
The goal of iden3auth libraries is to handle authentication messages of [communication protocol](https://iden3-communication.io/authorization/overview/).

Currently, library implementation includes support of next message types

Expand All @@ -25,7 +25,7 @@ Currently, library implementation includes support of next message types

Auth verification procedure:

1. JWZ token verification
1. [JWZ token](https://iden3-communication.io/proposals/jwz/overview/) verification
2. Zero-knowledge proof verification of request proofs
3. Query request verification for atomic circuits
4. Verification of identity and issuer states for atomic circuits
Expand All @@ -35,26 +35,26 @@ Auth verification procedure:
> Groth16 proof are supported by auth library
>

Verification keys must be provided using `KeyLoader` interface
Verification keys can be provided using `KeyLoader` interface or `EmbeddedKeyLoader` can be used to load keys embedded in the library `loaders/keys`.

### Query verification

Proof for each atomic circuit contains public signals that allow extracting user and issuer identifiers, states, signature, challenges, etc.

Circuit public signals marshallers are defined in the go-circuits library.
Circuit public signals marshallers are defined in the [go-circuits](https://github.com/iden3/go-circuits) library.

### Verification of user / issuer identity states

The blockchain verification algorithm is used

1. Gets state from the blockchain (address of id state contract and URL must be provided by the caller of the library):
1. Gets state from the blockchain ([address of id state contract](https://docs.iden3.io/contracts/contracts/) and RPC URL must be provided by the caller of the library):
1. Empty state is returned - it means that identity state hasn’t been updated or updated state hasn’t been published. We need to compare id and state. If they are different it’s not a genesis state of identity then it’s not valid.
2. The non-empty state is returned and equals to the state in provided proof which means that the user state is fresh enough and we work with the latest user state.
3. The non-empty state is returned and it’s not equal to the state that the user has provided. Gets the transition time of the state. The verification party can make a decision if it can accept this state based on that time frame.

2. Only latest states for user are valid. Any existing issuer state for claim issuance is valid.

### Verification of GIST
### Verification of [Global Identities State Tree(GIST)](https://docs.iden3.io/protocol/spec/#gist-new)

The blockchain verification algorithm is used

Expand Down Expand Up @@ -92,36 +92,84 @@ The blockchain verification algorithm is used
```
3. Token verification

Init Verifier configuration with embeded keys:

```go
resolvers := map[string]pubsignals.StateResolver{
"privado:main": state.ETHResolver{
RPCUrl: "https://rpc-mainnet.privado.id",
ContractAddress: common.HexToAddress("0x3C9acB2205Aa72A05F6D77d708b5Cf85FCa3a896"),
},
}
verifier, err := auth.NewVerifier(loaders.NewEmbeddedKeyLoader(), resolvers)

```
Init Verifier:

```go
var verificationKeyloader = &loaders.FSKeyLoader{Dir: keyDIR}
resolver := state.ETHResolver{
RPCUrl: <polygon_node_http>,
ContractAddress: <state_contract_address>,
}

resolvers := map[string]pubsignals.StateResolver{
"polygon:mumbai": resolver,
}
verifier,err := auth.NewVerifierWithExplicitError(verificationKeyloader, loaders.DefaultSchemaLoader{IpfsURL: "<IPFS NODE HERE>"}, resolvers)
// or use NewVerifier and check that verifier instance is not nil. IPFS merklization is not worked without setuping global loader
// verifier := auth.NewVerifier(verificationKeyloader, loaders.DefaultSchemaLoader{IpfsURL: "ipfs.io"}, resolvers)
var verificationKeyloader = &loaders.FSKeyLoader{Dir: keyDIR}
resolver := state.ETHResolver{
RPCUrl: <polygon_node_http>,
ContractAddress: <state_contract_address>,
}

resolvers := map[string]pubsignals.StateResolver{
"polygon:mumbai": resolver,
}
verifier,err := auth.NewVerifierWithExplicitError(verificationKeyloader, loaders.DefaultSchemaLoader{IpfsURL: "<IPFS NODE HERE>"}, resolvers)
// or use NewVerifier and check that verifier instance is not nil. IPFS merklization is not worked without setuping global loader
// verifier := auth.NewVerifier(verificationKeyloader, loaders.DefaultSchemaLoader{IpfsURL: "ipfs.io"}, resolvers)
```
4. FullVerify:

4. FullVerify - verify JWZ token generated by the client:

```go
```go
authResponse, err := verifier.FullVerify(
r.Context(),
string(tokenBytes),
authRequest.(protocolAuthorizationRequestMessage),
...VerifyOpt,
)
userId = authResponse.from // msg sender
```

```
Verify manually if thread id is used a session id to match request with `VerifyJWZ / VerifyAuthResponse` functions


### Examples of various supported configurations for different networks

- Addresses and networks of all supported state contracts can be found in the [iden3 documentation](https://docs.iden3.io/contracts/contracts/)
- For custom implementations and private networks you can deploy you own state contract [Contracts repository](https://github.com/iden3/contracts)
- It is recommended to use you own RPC nodes or third-party services like [Infura](https://infura.io) or [Alchemy](https://alchemyapi.io)

```go
const COMMON_STATE_ADDRESS = "0x3C9acB2205Aa72A05F6D77d708b5Cf85FCa3a896" // Common state address. Ethereum,
// Privado, Linea, zkEVM networks
const POLYGON_MAIN_STATE_ADDRESS = "0x624ce98D2d27b20b8f8d521723Df8fC4db71D79D" // Polygon main state address
const POLYGON_AMOY_STATE_ADDRESS = "0x1a4cC30f2aA0377b0c3bc9848766D90cb4404124" // // Polygon amoy state address

resolvers := map[string]pubsignals.StateResolver{
// Privado Identity Chain
"privado:main": state.NewETHResolver("https://rpc-mainnet.privado.id", COMMON_STATE_ADDRESS),
// Ethereum main
"ethereum:main": state.NewETHResolver("<RPC URL Ethereum main net>", COMMON_STATE_ADDRESS),
// Polygon POS main
"polygon:main": state.NewETHResolver("<RPC URL Polygon main net", POLYGON_MAIN_STATE_ADDRESS),
// Polygon zkEVM main
"zkevm:main": state.NewETHResolver("<RPC URL zkEVM main net>", COMMON_STATE_ADDRESS),
// Linea main
"linea:main": state.NewETHResolver("<RPC URL Linea main net>", COMMON_STATE_ADDRESS),
// Ethereum Sepolia
"ethereum:sepolia": state.NewETHResolver("<RPC URL Etherum seploia>", COMMON_STATE_ADDRESS),
// Polygon Amoy
"polygon:amoy": state.NewETHResolver("https://rpc-amoy.polygon.technology/", POLYGON_AMOY_STATE_ADDRESS),
// Polygon zkEVM Cardona
"zkevm:test": state.NewETHResolver("<RPC URL Polygon zkEVM net>", COMMON_STATE_ADDRESS),
// Linea-Sepolia
"linea:sepolia": state.NewETHResolver("RPC URL Linea sepolia"), COMMON_STATE_ADDRESS),
}

```

### Notes on prover optimization for x86_64 hardware

See readme in [iden3/go-rapidsnark/prover](https://github.com/iden3/go-rapidsnark/blob/main/prover/)
Expand Down
123 changes: 123 additions & 0 deletions loaders/embededKeyLoader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package loaders

import (
"embed"
"fmt"
"log/slog"
"sync"

"github.com/iden3/go-circuits/v2"
)

//go:embed keys/*.json
var defaultKeys embed.FS

// EmbeddedKeyLoader load keys from embedded FS or filesystem.
// Filesystem has priority if keyLoader specified.
type EmbeddedKeyLoader struct {
keyLoader VerificationKeyLoader
cache map[circuits.CircuitID][]byte
cacheMu sync.RWMutex
useCache bool
}

// NewEmbeddedKeyLoader creates a new loader with embedded keys
// By default, it uses embedded keys with caching enabled
// Use options to customize behavior:
// - WithKeyLoader to set custom loader
// - WithCacheDisabled to disable caching
//
// Example:
// Default configuration (embedded keys and enabled cache):
//
// loader := NewEmbeddedKeyLoader()
//
// Custom filesystem loader:
//
// fsLoader := &FSKeyLoader{Dir: "/path/to/keys"}
// loader := NewEmbeddedKeyLoader(WithKeyLoader(fsLoader))
//
// Disabled cache:
//
// loader := NewEmbeddedKeyLoader(WithCacheDisabled())
func NewEmbeddedKeyLoader(opts ...Option) *EmbeddedKeyLoader {
loader := &EmbeddedKeyLoader{
useCache: true, // enabled by default
cache: make(map[circuits.CircuitID][]byte),
}

// Apply options
for _, opt := range opts {
opt(loader)
}

return loader
}

// Option defines functional option for configuring EmbeddedKeyLoader
type Option func(*EmbeddedKeyLoader)

// WithKeyLoader sets a custom primary loader that will be tried before falling back to embedded keys
func WithKeyLoader(loader VerificationKeyLoader) Option {
return func(e *EmbeddedKeyLoader) {
e.keyLoader = loader
}
}

// WithCacheDisabled disables caching of loaded keys
func WithCacheDisabled() Option {
return func(e *EmbeddedKeyLoader) {
e.useCache = false
e.cache = nil
}
}

// Load attempts to load keys in the following order:
// 1. From cache if enabled and available
// 2. From keyLoader loader if provided
// 3. From embedded default keys
func (e *EmbeddedKeyLoader) Load(id circuits.CircuitID) ([]byte, error) {
// Try cache if enabled
if e.useCache {
if key := e.getFromCache(id); key != nil {
return key, nil
}
}

// Try keyLoader loader if provided
if e.keyLoader != nil {
key, err := e.keyLoader.Load(id)
if err == nil {
if e.useCache {
e.storeInCache(id, key)
}
return key, nil
}
slog.Warn("failed to load key from custom loader", "circuit_id", id, "error", err)
}

// Embedded keys
key, err := defaultKeys.ReadFile(fmt.Sprintf("keys/%v.json", id))
if err != nil {
return nil, fmt.Errorf("failed to load default key: %w", err)
}

if e.useCache {
e.storeInCache(id, key)
}
return key, nil
}

// getFromCache returns key from cache if available
func (e *EmbeddedKeyLoader) getFromCache(id circuits.CircuitID) []byte {
e.cacheMu.RLock()
defer e.cacheMu.RUnlock()
return e.cache[id]
}

// storeInCache stores key in cache
func (e *EmbeddedKeyLoader) storeInCache(id circuits.CircuitID, key []byte) {
e.cacheMu.Lock()
defer e.cacheMu.Unlock()
e.cache[id] = key
}
Loading
Loading