{"id":17806344,"url":"https://github.com/jrapoport/chestnut","last_synced_at":"2025-09-17T19:32:42.538Z","repository":{"id":55046636,"uuid":"328499453","full_name":"jrapoport/chestnut","owner":"jrapoport","description":"🌰 Chestnut is a powerful encrypted storage library for Go, featuring Sparse Encryption, a novel technique for selectively encrypting struct fields. It supports Chained Encryption, custom encryption (AES256-CTR), multiple storage backends (BBolt, NutsDB), and built-in compression (Zstandard), offering unmatched flexibility for secure data storage.","archived":false,"fork":false,"pushed_at":"2024-10-27T08:12:12.000Z","size":287,"stargazers_count":30,"open_issues_count":0,"forks_count":6,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-02T07:11:15.323Z","etag":null,"topics":["aes256-ctr","bbolt","chained-encryption","compression","custom-encryption","data-security","encryption","go","key-value-store","kv-store","nutsdb","secure-storage","sparse-encryption","zstandard"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jrapoport.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null}},"created_at":"2021-01-10T23:18:03.000Z","updated_at":"2025-03-11T23:13:13.000Z","dependencies_parsed_at":"2022-08-14T10:00:57.827Z","dependency_job_id":null,"html_url":"https://github.com/jrapoport/chestnut","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jrapoport/chestnut","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jrapoport%2Fchestnut","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jrapoport%2Fchestnut/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jrapoport%2Fchestnut/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jrapoport%2Fchestnut/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jrapoport","download_url":"https://codeload.github.com/jrapoport/chestnut/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jrapoport%2Fchestnut/sbom","scorecard":{"id":536515,"data":{"date":"2025-08-11","repo":{"name":"github.com/jrapoport/chestnut","commit":"953a7bc277b485e0de7da2421329161272754d95"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.5,"checks":[{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Code-Review","score":0,"reason":"Found 0/12 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/fossa.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/jrapoport/chestnut/fossa.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/fossa.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/jrapoport/chestnut/fossa.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/jrapoport/chestnut/test.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/jrapoport/chestnut/test.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/test.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/jrapoport/chestnut/test.yml/master?enable=pin","Info:   0 out of   3 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/fossa.yml:1","Warn: no topLevel permission defined: .github/workflows/test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":3,"reason":"branch protection is not maximal on development and all release branches","details":["Info: 'allow deletion' disabled on branch 'master'","Info: 'force pushes' disabled on branch 'master'","Info: 'branch protection settings apply to administrators' is required to merge on branch 'master'","Warn: could not determine whether codeowners review is allowed","Warn: no status checks found to merge onto branch 'master'","Warn: PRs are not required to make changes on branch 'master'; or we don't have data to detect it.If you think it might be the latter, make sure to run Scorecard with a PAT or use Repo Rules (that are always public) instead of Branch Protection settings"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 8 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":8,"reason":"2 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GO-2024-3321 / GHSA-v778-237x-gjrc","Warn: Project is vulnerable to: GO-2025-3487 / GHSA-hcg3-q754-cr77"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-20T07:08:24.933Z","repository_id":55046636,"created_at":"2025-08-20T07:08:24.933Z","updated_at":"2025-08-20T07:08:24.933Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275649909,"owners_count":25503212,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-17T02:00:09.119Z","response_time":84,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aes256-ctr","bbolt","chained-encryption","compression","custom-encryption","data-security","encryption","go","key-value-store","kv-store","nutsdb","secure-storage","sparse-encryption","zstandard"],"created_at":"2024-10-27T13:05:27.384Z","updated_at":"2025-09-17T19:32:42.492Z","avatar_url":"https://github.com/jrapoport.png","language":"Go","readme":"# 🌰 \u0026nbsp;Chestnut\n\n![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/jrapoport/chestnut/test.yml?branch=master\u0026style=flat-square) \n[![Go Report Card](https://goreportcard.com/badge/github.com/jrapoport/chestnut?style=flat-square\u0026)](https://goreportcard.com/report/github.com/jrapoport/chestnut) \n[![Codecov branch](https://img.shields.io/codecov/c/github/jrapoport/chestnut/master?style=flat-square\u0026token=7REY4BDPHW)](https://codecov.io/gh/jrapoport/chestnut)\n![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/jrapoport/chestnut?style=flat-square) \n[![GitHub](https://img.shields.io/github/license/jrapoport/chestnut?style=flat-square)](https://github.com/jrapoport/chestnut/blob/master/LICENSE)\n\n[![Buy Me A Coffee](https://img.shields.io/badge/buy%20me%20a%20coffee-☕-6F4E37?style=flat-square)](https://www.buymeacoffee.com/jrapoport)\n\n\nChestnut is encrypted storage for Go. The goal was an easy to use encrypted \nstore with helpful features that was quick to set up, but highly flexible. \n\nChestnut is written in pure go and designed **not** to have strong opinions \nabout things like storage, compression, hashing, secrets, or encryption. \nChestnut is a storage chest, and not a datastore itself. As such, Chestnut must \nbe backed by a storage solution. \n\nCurrently, Chestnut supports [BBolt](https://github.com/etcd-io/bbolt) and\n[NutsDB](https://github.com/nutsdb/nutsdb) as backing storage.\n\n## Table of Contents\n- [Getting Started](#getting-started)\n    * [Installing](#installing)\n    * [Importing Chestnut](#importing-chestnut)\n        + [Requirements](#requirements)\n- [Storage](#storage)\n    * [Built-in](#supported)\n        + [BBolt](#bbolt)\n        + [NutsDB](#nutsdb)\n    * [Planned](#planned)\n- [Encryption](#encryption)\n    * [AES256-CTR](#aes256-ctr)\n    * [Custom Encryption](#custom-encryption)\n    * [Chained Encryption](#chained-encryption)\n    * [Sparse Encryption](#sparse-encryption)\n        + [What is \"sparse\" encryption?](#what-is--sparse--encryption-)\n        + [Enabling Sparse Encryption](#enabling-sparse-encryption)\n        + [Using Sparse Encryption](#using-sparse-encryption)\n            - [Sparse Loading](#sparse-loading)\n            - [Decryption](#decryption)\n- [Secrets](#secrets)\n    + [TextSecret](#textsecret)\n    + [ManagedSecret](#managedsecret)\n    + [SecureSecret](#securesecret)\n- [Compression](#compression)\n    * [Zstandard](#zstandard)\n    * [Custom Compression](#custom-compression)\n    * [Compression + Sparse Encryption](#compression---sparse-encryption)\n- [Operations](#operations)\n    * [Basic Operations](#basic-operations)\n        + [Put](#put)\n        + [Get](#get)\n        + [Delete](#delete)\n    * [Struct Operations](#struct-operations)\n        + [Save](#save)\n        + [Load](#load)\n        + [Sparse](#sparse)\n    * [Keyed Operations](#keyed-operations)\n        + [SaveKeyed](#savekeyed)\n        + [LoadKeyed](#loadkeyed)\n        + [SparseKeyed](#sparsekeyed)\n    * [Extra Operations](#extra-operations)\n        + [Has](#has)\n        + [List](#list)\n        + [Export](#export)\n- [Struct Field Tags](#struct-field-tags)\n    * [Secure](#secure)\n    * [Hash](#hash)\n        + [SHA256](#sha256)\n        + [Hash Prefix](#hash-prefix)\n    * [Multiple Tags](#multiple-tags)\n- [Disable Overwrites](#disable-overwrites)\n- [Keystore](#keystore)\n    * [Importing Keystore](#importing-keystore)\n    * [Important Note](#important-note)\n- [Logging](#logging)\n    + [Logrus Logger](#logrus-logger)\n    + [Zap Logger](#zap-logger)\n    + [Standard Logger](#standard-logger)\n    + [Storage](#storage-1)\n- [Examples](#examples)\n- [Known Issues](#known-issues)\n- [Misc](#misc)\n    * [JSON encoding](#json-encoding)\n\n## Getting Started\n\n### Installing\n\nTo start using Chestnut, install Go (version 1.11+) and run `go get`:\n\n```sh\n$ go get -u github.com/jrapoport/chestnut\n```\n\n### Importing Chestnut\n\nTo use Chestnut as an encrypted store, import as:\n\n```go\nimport (\n  \"github.com/jrapoport/chestnut\"\n  \"github.com/jrapoport/chestnut/encryptor/aes\"\n  \"github.com/jrapoport/chestnut/encryptor/crypto\"\n  \"github.com/jrapoport/chestnut/storage/nuts\"\n)\n\n// use nutsdb for storage\nstore := nuts.NewStore(path)\n\n// use AES256-CFB for encryption\nopt := chestnut.WithAES(crypto.Key256, aes.CFB, mySecret)\n\ncn := chestnut.NewChestnut(store, opt)\nif err := cn.Open(); err != nil {\n    return err\n}\n\ndefer cn.Close()\n\n```\n\n#### Requirements\nChestnut has two requirements:\n1) [Storage](#storage) that supports the `storage.Storage` interface \n   (with a lightweight adapter).\n2) [Encryption](#encryption) which supports the `crypto.Encryptor` interface.\n\n## Storage\nChestnut will work seamlessly with **any** storage solution (or adapter) that \nsupports the`storage.Storage` interface.\n\n### Built-in\n\nCurrently, Chestnut has built-in support for\n[BBolt](https://github.com/etcd-io/bbolt) and \n[NutsDB](https://github.com/nutsdb/nutsdb).\n\n#### BBolt\n\nhttps://github.com/etcd-io/bbolt\nChestnut has built-in support for using\n[BBolt](https://github.com/etcd-io/bbolt) as a backing store.\n\nTo use bbolt for a backing store you can import Chestnut's `bolt` package\nand call `bolt.NewStore()`:\n\n```go\nimport \"github.com/jrapoport/chestnut/storage/bolt\"\n\n//use or create a bbolt backing store at path\nstore := bolt.NewStore(path)\n\n// use bbolt for the storage chest\ncn := chestnut.NewChestnut(store, ...)\n```\n \n#### NutsDB\n\nhttps://github.com/nutsdb/nutsdb  \nChestnut has built-in support for using \n[NutsDB](https://github.com/nutsdb/nutsdb) as a backing store.  \n\nTo use nutsDB for a backing store you can import Chestnut's `nuts` package\nand call `nuts.NewStore()`:\n\n```go\nimport \"github.com/jrapoport/chestnut/storage/nuts\"\n\n//use or create a nutsdb backing store at path\nstore := nuts.NewStore(path)\n\n// use nutsdb for the storage chest\ncn := chestnut.NewChestnut(store, ...)\n```\n\n### Planned\n\nOther K/V stores like LevelDB.\n\n[GORM](https://github.com/go-gorm/gorm) (probably not)\n  Gorm is an ORM, so while it's not a datastore per se, it could be adapted \n  to support sparse encryption and would mean automatic support for databases \n  like mysql, sqlite, etc. However, most (if not all) of those DBs *already* \n  support built-in encryption, so w/o some compelling use-case that's not \n  already covered I don't see a lot of value-add.\n\n## Encryption\nChestnut supports several flavors of AES out of the box:\n* AES128-CFB, AES192-CFB, and AES256-CFB\n* AES128-CTR, AES192-CTR, and AES256-CTR\n* AES128-GCM, AES192-GCM, and AES256-GCM\n\nYou can add AES encryption to Chestnut by passing the `chestnut.WithAES()` option:\n```go\nopt := chestnut.WithAES(crypto.Key256, aes.CFB, mySecret)\n```\n\n### AES256-CTR\nFor encryption we recommend using AES256-CTR. We chose AES256-CTR based in part\non this [helpful analysis](https://www.highgo.ca/2019/08/08/the-difference-in-five-modes-in-the-aes-encryption-algorithm/)\nfrom Shawn Wang, PostgreSQL Database Core.\n\n### Custom Encryption\nChestnut supports drop-in custom encryption. A struct that supports the \n`crypto.Encryptor` interface can be used with the `chestnut.WithEncryptor()` \noption. \n\nSupporting `crypto.Encryptor` interface is straightforward and mainly consists \nof vending the following two methods:\n\n```go\n// Encrypt returns data encrypted with the secret.\nEncrypt(plaintext []byte) (ciphertext []byte, err error)\n\n// Decrypt returns data decrypted with the secret.\nDecrypt(ciphertext []byte) (plaintext []byte, err error)\n```\n\n### Chained Encryption\nChestnut also supports chained encryption which allows data to be arbitrarily \ntransformed by a chain of Encryptors in a FIFO order.\n\nA chain of `crypto.Encryptor`s can be passed to Chestnut with the \n`chestnut.WithEncryptorChain` option:\n\n```go\nopt := chestnut.WithEncryptorChain(\n    encryptor.NewAESEncryptor(crypto.Key128, aes.CFB, secret1),\n    encryptor.NewAESEncryptor(crypto.Key192, aes.CTR, secret2),\n    encryptor.NewAESEncryptor(crypto.Key256, aes.GCM, secret3),\n)\n```\n\nor by using a `crypto.ChainEncryptor` with the `chestnut.WithEncryptor` option:\n\n```go\nencryptors := []crypto.Encryptor{\n    encryptor.NewAESEncryptor(crypto.Key128, aes.CFB, secret1),\n    encryptor.NewAESEncryptor(crypto.Key192, aes.CTR, secret2),\n    encryptor.NewAESEncryptor(crypto.Key256, aes.GCM, secret3),\n}\nchain := crypto.NewChainEncryptor(encryptors...)\nopt := chestnut.WithEncryptor(chain)\n```\n\nIf you use both the `chestnut.WithEncryptor` and the\n`chestnut.WithEncryptorChain` options, the `crypto.Encryptor` from\n`chestnut.WithEncryptor` will be **prepended*** to the chain.\n\n### Sparse Encryption\nChestnut supports the sparse encryption of structs.\n\nSparse encryption is a transparent feature of saving structs with\n`Chestnut.Save()`, `Chestnut.Load()`, and `Chestnut.Sparse()`; or structs that\nsupport the `value.Keyed` interface with `Chestnut.SaveKeyed()`,\n`Chestnut.LoadKeyed()`, and `Chestnut.SparseKeyed()`.\n\n#### What is \"sparse\" encryption?\nWith sparse encryption, only struct fields marked as `secure` will be encrypted.\nThe remaining \"plaintext\" fields are encoded and stored separately. \n\nThis allows you to load a \"sparse\" copy of the struct by calling\n`Chestnut.Sparse()` or `Chestnut.SparseKeyed()` (if you have a `value.Keyed`\nvalue) and examine the plaintext fields **without** the overhead of decryption.\nWhen a sparse struct is loaded, *the contents of struct fields marked as\n`secure` are replaced by empty values*.\n\n#### Enabling Sparse Encryption\nChestnut uses struct tags to indicate which specific struct fields should be \nencrypted. To enable sparse encryption for a struct, add the `secure` tag option\nto the JSON tag of *at least one* struct field:\n\n```go\nSecretKey string `json:\",secure\"` // 'secure' option (bare minimum)\n```\nlike so: \n\n```go\ntype MySparseStruct struct {\n    SecretValue string `json:\"secret_value,secure\"` // \u003c-- add 'secure' here\n    PublicValue string `json:\"public_value\"`\n}\n```\n\n#### Using Sparse Encryption\nStructs can be sparsely encrypted by calling `Chestnut.Save()`, or if the struct \nsupports the `value.Keyed` interface, `Chestnut.SaveKeyed()`. Chestnut will \nautomatically detect the `secure` tag and do the rest. \n\n**If no `secure` fields are found, Chestnut will encrypt the entire struct.**\n\n```go\nsparseObj := \u0026MySparseStruct{\n    SecretValue: \"this is a secret\",\n    PublicValue: \"this is public\",\n}\n\nerr := cn.Save(\"my-namespace\",  []byte(\"my-key\"), sparseObj)\n```\n\nWhen `MySparseStruct` is saved, Chestnut will detect the `secure` struct field\nand **only encrypt** those fields. Any remaining fields will be encoded as\nplaintext. In the case of `MySparseStruct`this means that `SecretValue`**will\nbe** encrypted prior to being encoded, and `PublicValue`**will not** be\nencrypted.\n\n##### Sparse Loading \nA sparse struct can be loaded by calling `Chestnut.Sparse()`, or if the struct\nsupports the `value.Keyed` interface, `Chestnut.SparseKeyed()`. When these\nmethods are called to load a sparsely encrypted struct, a partially decoded \nstruct will be returned, but the no decryption will occur. Secure fields will \ninstead be replaced by empty values.\n\n```go\nsparseObj := \u0026MySparseStruct{}\n\nerr := cn.Sparse(\"my-namespace\",  []byte(\"my-key\"), sparseObj)\n```\n\nExamining the struct will reveal that the `secure` fields were replaced with \nempty values, and not decrypted.\n\n```go\n*MySparseStruct{\n    SecretValue: \"\"\n    PublicValue: \"this is public\"\n}\n```\n\n**Only sparsely encrypted structs can be sparsely loaded**  \nIf `Chestnut.Sparse()` or `Chestnut.SparseKeyed()` is called on a struct that \nwas not sparsely encrypted, the fully decrypted struct will be returned.\n\n##### Decryption\n\nA sparsely encrypted struct can be fully decrypted by calling `Chestnut.Load()`,\nor if the struct supports the `value.Keyed` interface,\n`Chestnut.LoadKeyed()`. When any of those methods are called on a sparsely\nencrypted struct, a fully decrpted copy of the struct is returned.\n\n## Secrets\n\nChestnut secrets are handled through the `crypto.Secret` interface. The \n`crypto.Secret` interface is designed to provide a high degree of flexibility \naround how you store, retrieve, and manage the secrets you use for encryption. \n\nWhile Chestnut currently only comes with AES symmetric key encryption, the\n`crypto.Secret` interface can easily be adapted to support other forms of \nencryption like a private key-based `crypto.Encryptor`.\n\nChestnut currently provides three basic immplementations of the `crypto.Secret` \ninterface which should cover most cases.\n\n#### TextSecret\n\n`crypto.TextSecret` provides a lightweight wrapper around a plaintext `string`.\n\n```go\ntextSecret := crypto.NewTextSecret(\"a-secret\")\n```\n\n#### ManagedSecret\n\n`crypto.ManagedSecret` provides a unique ID alongside a plaintext `string`\nsecret. You can use this id to securely track the secret if you use external\nvaults or functionality like rollover.\n\n```go\nmanagedSecret := crypto.NewManagedSecret(\"my-secret-id\", \"a-secret\")\n```\n\n#### SecureSecret\n\n`crypto.SecureSecret` provides a unique id for a secret alongside an\n`openSecret()` callback which returns a byte representation of the secret for\nencryption and decryption on `SecureSecret.Open()`. When `crypto.SecureSecret`\ncalls `openSecret()` it will pass a copy of itself as a `crypto.Secret`. This\nallows for remote loading of the secret based on its id, or using a secure\nin-memory storage solution for the secret like\n[memguarded](https://github.com/n0rad/memguarded).\n\n```go\nopenSecret := func(s crypto.Secret) []byte {\n\t// fetch the secret \n    mySecret := getMySecretFromTheVault(s.ID())\n    return mySecret\n}\nsecureSecret := crypto.NewSecureSecret(\"my-secret-id\", openSecret)\n```\n\n## Compression\n\nChestnut supports compression of the encoded data. Compression takes place\n*prior to* encryption.\n\nCompression can be enabled through the `chestnut.WithCompression` option and \npassing it a supported compression format:\n\n```go\nopt := chestnut.WithCompression(compress.Zstd)\n```\n\nData compressed while `chestnut.WithCompression` is active with a supported\ncompression format will continue to be correctly decompressed when read *even\nif* compression is no longer active (i.e. `chestnut.WithCompression` is no\nlonger being used). This is not true with custom compression. Data compressed\nusing custom compression cannot be decompressed if that custom compression is\ndisabled.\n\n### Zstandard\n\nChestnut currently supports [Zstandard](https://facebook.github.io/zstd/)\ncompression out of the box with the `compress.Zstd` format option. To enable \nZstandard compression, call `chestnut.WithCompression` passing `compress.Zstd`\nas the compression format:\n\n```go\nopt := chestnut.WithCompression(compress.Zstd)\n```\n\nPlease Note: I have no affiliation with Facebook (past or present) and just \nliked this compression format.\n\n### Custom Compression\n\nIf you wish to supply your own compression routines you can do so easily with\nthe `chestnut.WithCompressors` option:\n\n```go\nopt := chestnut.WithCompressors(myCompressorFn, myDecompressorFn)\n```\n\nYour two custom compression functions, a compressor `compress.CompressorFunc`, \nand a decompressor `compress.DecompressorFunc` must have the following format:\n\n```go\nCompressor(data []byte) (compressed []byte, err error)\n\nDecompressor(compressed []byte) (data []byte, err error)\n```\n\n### Compression + Sparse Encryption\n\nEnabling compression will *not* affect sparse encryption. Sparsely encrypted\nvalues compress their secure and plaintext encodings independently.\n\n## Operations\n\nChestnut supports all basic CRUD operations with a few extras.\n\nAll `WRITE` operations: `Chestnut.Put()`, `Chestnut.Save()`, \u0026\n`Chestnut.SaveKeyed()`, will encrypt data prior to it being stored.\n\nAll `READ` operations: `Chestnut.Get()`, `Chestnut.Load()`, \u0026\n`Chestnut.LoadKeyed()`, will decrypt data prior to it being returned.\n\nAll `SPARSE` operations: `Chestnut.Sparse()`, \u0026 `Chestnut.SparseKeyed()`,\nwill **not** decrypt data prior to it being returned.\n\n**In all cases no record of the plaintext data is kept**  \n(even with DebugLevel logging enabled).\n\n### Basic Operations\n\n#### Put\n\nTo save an encrypted value to a namespaced key in the storage chest, use the \n`Chestnut.Put()` function:\n\n```go\nerr := cn.Put(\"my-namespace\", []byte(\"my-key\"), []byte(\"plaintext\"))\n```\n\nThis will set the value of the `\"my-key\"` key to the encrypted ciphertext of \n`\"plaintext\"` in the `my-namespace` namespace. If a namespace does not exist,\nit will be automatically created.\n\nIf the key already exists, and the storage chest was initialized with the \n`chestnut.OverwritesForbidden` option, this call will fail with ErrForbidden.\n\nTo retrieve this value, we can use the `Chestnut.Get()` function:\n\n#### Get\n\nTo retrieve a decrypted value from a namespaced key in the storage chest, we can \nuse the `Chestnut.Get()` function:\n\n```go\nplaintext, err := cn.Get(\"my-namespace\", []byte(\"my-key\"))\n```\n\n#### Delete\n\nUse the `Chestnut.Delete()` function to delete a key from the store.\n\n```go\nerr := cn.Delete(\"my-namespace\", []byte(\"my-key\"))\n```\n\n### Struct Operations\n\nChestnut provides several functions for working directly with structs. In\naddition to handling the marshalling, encoding and encryption of structs for\nyou, these functions provide automatic support for the Chestnut struct field tag\noptions `secure` and `hash`. SEE: [Struct Field Tags](#struct-field-tags) for\nmore detail.\n\n#### Save\n\nTo encrypt and save a struct to the store we can use the `Chestnut.Save()` \nfunction:\n\n```go\nerr := cn.Save(\"my-namespace\", []byte(\"my-key\"), myStruct)\n```\n\nChestnut will marshal and encrypt the encoded byte representation. If the struct\nsupports the `secure` struct field tag option on one of its fields, Chestnut \nwill automatically sparsely encrypt the struct. Other supported struct field tag\noptions will also be applied. \n\n#### Load\n\nTo retrieve the fully decrypted struct, we can use the `Chestnut.Load()` \nfunction:\n\n```go\nerr := cn.Load(\"my-namespace\", []byte(\"my-key\"), \u0026myStruct)\n```\n\n#### Sparse\n\n`Chestnut.Sparse()` loads the struct at key and returns the sparsely decoded\nresult. Unlike `Chestnut.Load()`, it **does not decrypt** the encoded struct and\nsecure fields are replaced with empty values. To retrieve a sparse value, we\ncan use the `Chestnut.Sparse()` function:\n\n```go\nerr := cn.Sparse(\"my-namespace\", []byte(\"my-key\"), \u0026myStruct)\n```\n\nIf the struct was not saved as a sparsely encoded struct this has no effect and\nis equivalent to calling `Chestnut.Load()`. Structs must have been saved with\nsecure fields to be loaded as sparse structs by `Chestnut.Sparse()`.\n\nWhen a sparse struct is returned, any fields marked as `secure` will be decoded  \nas nil or empty values. For more information, please see the section on \n[sparse encryption](#sparse-encryption).\n\n### Keyed Operations\n\nChestnut provides several convenience functions for working with struct values\nthat support the `value.Keyed` interface. Keyed values can supply their own \nnamespace and keys via calls to `Keyed.Namespace()` and `Keyed.Keys()`, \nrespectively. Internally these functions are equivalent to calling \n`Chestnut.Save()`, `Chestnut.Load()`, and `Chestnut.Sparse()` with an explicit \nnamespace and key.\n\n#### SaveKeyed\n\nTo encrypt and store a struct that implements the `value.Keyed` interface to\nthe store we can use the `Chestnut.SaveKeyed()` function:\n\n```go\nerr := cn.SaveKeyed(myKeyedStruct)\n```\n\nTo save a keyed struct with `Chestnut.SaveKeyed()`, the struct must be \ninitialized with namespace and key you want to save it to *prior to* calling \n`Chestnut.LoadKeyed()` in order to satisfy the `value.Keyed` interface:\n\n```go\nko := MyKeyedValue{ name: \"my-namespase\", key: \"my key\"}\nerr := cn.SaveKeyed(\u0026ko)\n```\n\nFor more information, please see [Chestnut.Save()](#save)\n\n#### LoadKeyed\n\nTo retrieve the fully decrypted struct that implements the `value.Keyed` \ninterface, we can use the `Chestnut.LoadKeyed()` function:\n\n```go\nerr := cn.LoadKeyed(\u0026myKeyedStruct)\n```\n\nTo load a keyed struct with `Chestnut.LoadKeyed()`, the struct must be\ninitialized with namespace and key you want to retrieve *prior to* calling\n`Chestnut.LoadKeyed()` in order to satisfy the `value.Keyed` interface:\n\n```go\nko := MyKeyedValue{ name: \"my-namespase\", key: \"my key\"}\nerr := cn.LoadKeyed(\u0026ko)\n```\n\nFor more information, please see [Chestnut.Load()](#load)\n\n#### SparseKeyed\n\nTo retrieve a sparsely encrypted struct that implements the `value.Keyed`\ninterface, we can use the `Chestnut.SparseKeyed()` function:\n\n```go\nerr := cn.SparseKeyed(\u0026myKeyedStruct)\n```\n\nTo load a sparse keyed struct with `Chestnut.SparseKeyed()`, the struct must be\ninitialized with namespace and key you want to retrieve *prior to* calling\n`Chestnut.SparseKeyed()` in order to satisfy the `value.Keyed` interface:\n\n```go\nko := MyKeyedValue{ name: \"my-namespase\", key: \"my key\"}\nerr := cn.SparseKeyed(\u0026ko)\n```\n\nFor more information, please see [Chestnut.Sparse()](#sparse)\n\n### Extra Operations\n\nChestnut supports a few additional functions that you might find helpful. In the \nfuture more may be added assuming they can be reasonably supported by the\n`storage.Storage` interface and generally make sense to do so. If there is a \nspecific function you'd like to see added, please feel free to open an issue \nrequest to discuss.\n\n#### Has\n\nYou can check to see if a key exists by calling `Chestnut.Has()`. If the key is\nfound, it will return true, otherwise false. If an error occured,\n`Chestnut.Has()` will return false along with the error.\n\n```go\nhas, err := cn.Has(\"my-namespace\", []byte(\"my-key\"))\n```\n\n#### List\n\nTo get a list of all the keys for a namespace you can call `Chestnut.List()`:\n\n```go\nkeys, err := cn.List(\"my-namespace\")\n```\n\n#### ListAll\n\nTo get a mapped list of all keys in the store organized by namespace you can call\n`Chestnut.ListAll()`:\n\n```go\nkeymap, err := cn.ListAll()\n```\n\n#### Export\n\nTo export the storage chest to another path you can call `Chestnut.Export()`:\n\n```go\nerr := cn.Export(\"/a/path/someplace\")\n```\n\nChestnut cannot be exported to its current location. If you call \n`Chestnut.Export()` and pass the path to Chestnut's current location an error\nwill be returned.\n\n## Struct Field Tags\n\nChestnut currently supports two extensions to the `` `json` `` struct field tag\nas options: `secure` when added to the `` `json` `` tag, `secure` marks the\nfield for sparse encryption. `hash` when added to the `` `json` `` tag, `hash`\nmarks *a string* field for hashing.\n\nThese options will be automatically detected and applied when the struct is\nsaved with `Chestnut.Save()`, or `Chestnut.SaveKeyed()`.\n\n**NOTE:** The order in which the tag options appear is unimportant.\n\n```go\n// these are equivalent\n\n`json:\"my_value,secure,omitempty\"`\n\n`json:\"my_value,omitempty,secure\"`\n```\n\nIn the future, Chestnut will also support its own struct field tag. `` `cn` ``.\n\n### Secure\n\nWhen the `secure` option is added to a `` `json` `` struct field tag, the struct\nfield is marked for sparse encryption. If Chestnut detects a `secure` option on\na struct field tag, **only** those fields marked with `secure` will be encrypted.\nIf no `secure` fields are found, Chestnut will encrypt the entire struct.\n\nTo mark a struct field as secure, just add `secure` as an option to a `` `json`\n`` struct field tag (like `omitempty`). The following are some examples of how\nthe `secure` option can be added to the `` `json` `` struct field tag:\n\n```go\ntype MySecureStruct struct {\n    ValueA     int      `json:\",secure\"`           // *will* be encrypted\n    ValueB     struct{} `json:\"value_b,secure\"`    // *will* be encrypted\n    ValueC     string   `json:\",omitempty,secure\"` // *will* be encrypted\n    PlaintextA string                              // will *not* be encrypted\n    PlaintextB int      `json:\"\"`                  // will *not* be encrypted\n    PlaintextC int      `json:\"-\"`                 // will *not* be encrypted\n    privateA   int      `json:\",secure\"`           // will *not* be encrypted\n}\n\n```\n\nFields marked with `secure` are encrypted hierarchically, meaning if you have:\n\n```go\npackage main\n\ntype MyStructA struct {\n\tValueA string `json:\"value_a,secure\"`    // *will* be encrypted\n}\n\ntype MyStructB struct {\n\tMyStructA                                // will *not* be encrypted\n\tValueB    string `json:\"value_b\"`        // will *not* be encrypted\n}\n\ntype MyStructC struct {\n\tMyStructA                                // will *not* be encrypted\n\tValueC    string `json:\"value_c\"`        // will *not* be encrypted\n}\n\ntype MyStructD struct {\n\tValueD string    `json:\"value_d,secure\"` // *will* be encrypted\n\tEmbed1 MyStructA                         // will *not* be encrypted\n\tEmbed2 MyStructB                         // will *not* be encrypted\n\tEmbed3 MyStructB `json:\"embed_3,secure\"` // *will* be encrypted\n}\n\nvar myStruct = \u0026MyStructD{\n\tValueD: \"foo\",\n\tEmbed1: MyStructA{\n\t\tValueA: \"bar\",\n\t},\n\tEmbed2: MyStructB{\n\t\tMyStructA: MyStructA{\n\t\t\tValueA: \"quack\",\n\t\t},\n\t\tValueB: \"baz\",\n\t},\n\tEmbed3: MyStructB{\n\t\tMyStructA: MyStructA{\n\t\t\tValueA: \"foobar\",\n\t\t},\n\t\tValueB: \"bonk\",\n\t},\n}\n```\n\n`myStruct` will be encrypted by Chestnut as:\n\n```go\n*MyStructD {\n  ValueD: ****\n  Embed1: main.MyStructA{\n      ValueA: ****\n  },\n  Embed2: main.MyStructB{\n      MyStructA: main.MyStructA{\n      \tValueA: ****\n      },\n      ValueB: ****\n  },\n  Embed3: ****\n}\n```\nwhere `'****'` represents an encrypted value.\n\nPlease see [Sparse Encryption](#sparse-encryption) for more information.\n\n### Hash\n\nWhen the `hash` option is added to a `` `json` `` struct field tag of a `string`\nfield, the `string` field is marked for hashing. If Chestnut detects a `hash` \noption on a `string` field, the string value of the field will be replaced with \nits hash.\n\nIf the `hash` option is applied to a struct field that is not type `string`, it \nis ignored.\n\nTo hash a string field of a struct, just add `hash` as an option to a `` `json`\n`` struct field tag (like `omitempty`). The following are some examples of how\nthe `hash` option can be added to the `` `json` `` struct field tag:\n\n```go\ntype MyHashStruct struct {\n    ValueA     string   `json:\",hash\"`           // *will* be hashed\n    ValueB     string   `json:\"value_b,hash\"`    // *will* be hashed\n    ValueC     string   `json:\",omitempty,hash\"` // *will* be hashed\n    ValueD     string   `json:\",hash,omitempty\"` // *will* be hashed\n    ...\n    Count      int      `json:\"count,hash\"`      // will *not* be hashed\n}\n```\n\nTaking the above struct as an example:\n\n```go\nvar myHashStruct = \u0026MyHashStruct {\n    ValueA: \"value a\",\n    ValueB  \"value b\",\n    ValueC  \"value c\",\n    ValueD  \"value d\",\n    ...\n    Count   42,\n}\n```\n\n`myHashStruct` will be encoded as:\n\n```go\n*main.MyHashStruct {\n    ValueA: \"sha256:BEBA1D9847D6E595D8DD6832DEE5432916C6F7AE438BC9A99C5BAFDD0E93793E\"\n    ValueB  \"sha256:2A53D83488A34E898436908A7064276859FFF56F69D16E2F61573057EDEBFB64\"\n    ValueC  \"sha256:80C92DE321A1CB8AEA3025890DE39A5BAA95A91DF022B10E1A71BEABB8BCC1BE\"\n    ValueD  \"sha256:8080378350428FABDE1724D9B920D613B8920A71151F1A9AF37EB4AF43628AE4\"\n\t...\n    Count   42\n}\n```\n\n#### SHA256\n\nChestnut currently supports SHA256 for hashing. In the future the `hash` option\nmay be extending to include an algorithm name e.g.:\n\n```\n`json:\"some_field,hash=sha3-256\"`\n```\n\nin which which case `hash` would continue to default to `sha256`.\n\n#### Hash Prefix\n\nHashed struct fields will have the algorithm used to hash the value pre-pended\nto the hash string:\n\n```properties\nsha256:BEBA1D9847D6E595D8DD6832DEE5432916C6F7AE438BC9A99C5BAFDD0E93793E\n```\n\nChestnut uses the `[hash alogorithm name]:` prefix to know that it has *already* \nhashed the value, and it should hash it again when the struct is saved. \n\n**IMPORTANT!** Changing or removing the hash prefix will cause Chestnut to \n**rehash the value** of the struct field the next time the struct is saved.\n\n### Multiple Tags\n\nChestnut supports the combining of tag options. You are free to mark a struct \nfield as both `secure` and `hash`:\n\n```go\ntype MyCombinedStruct struct {\n    ValueA     string   `json:\"value_a,secure,hash\"` // will be hashed *AND* encrypted \n    ...\n}\n```\nAs with other tag options, the order in which they appear is unimportant. \n\n**However**, the order in which Chestnut **applies** them is **fixed**. A field\nmarked with both `secure` and `hash` will be **first** be hashed, and **then** \nencrypted. This order of operations cannot be changed for obvious reasons.\n\n## Disable Overwrites\n\nChestnut supports the disabling of overwrites via the\n`chestnut.OverwritesForbidden` option.\n\n```go\ncn := chestnut.NewChestnut(store, encryptor chestnut.OverwritesForbidden())\n```\nWhen this option is set, once a value has been saved to a namespaced key,\nsuccessive calls to save a value to the same key will fail with ErrForbidden.\n\nThe key must be explicitly deleted before a new call to save a value for the\nsame key will succeed.\n\n## Keystore\n\nChestnut includes an implementation of IPFS compliant keystore which can be\nfound [here](keystore). \n\n### Importing Keystore\n\nUsing the Keystore is straight forward:\n\n```go\npackage main\n\nimport (\n\t\"github.com/jrapoport/chestnut\"\n\t\"github.com/jrapoport/chestnut/encryptor/aes\"\n\t\"github.com/jrapoport/chestnut/encryptor/crypto\"\n\t\"github.com/jrapoport/chestnut/keystore\"\n\t\"github.com/jrapoport/chestnut/storage/nuts\"\n)\n\n// use nutsdb\nstore := nuts.NewStore(path)\n\n// use a simple text secret\ntextSecret := crypto.TextSecret(\"i-am-a-good-secret\")\n\n// use AES256-CFB encryption\nopt := chestnut.WithAES(crypto.Key256, aes.CFB, textSecret)\n\n// open the keystore with nutsdb and the aes encryptor\nks := keystore.NewKeystore(store, opt)\nif err := ks.Open(); err != nil {\n    return err\n}\n```\n\nA complete example of the Chestnut `Keystore` can be found\n[here](examples/keystore).\n\n### Important Note\n\n```go\npackage main\n\nimport  (\n    \"github.com/ipfs/go-ipfs/keystore\"\n    \"github.com/libp2p/go-libp2p/core/crypto\"\n)\n```\n\nIf you want to work with the Keystore, please make **make sure** you are\nimporting \n[go-ipfs](github.com/ipfs/go-ipfs) and\n[go-libp2p-core](https://github.com/libp2p/go-libp2p-core/), and NOT importing\n[go-ipfs-keystore](github.com/ipfs/go-ipfs-keystore) and\n[go-libp2p-crypto](github.com/libp2p/go-libp2p-crypto) — which are\n**DEPRECATED**, out of date, a/o archived, etc.\n\n## Logging\n\nChestnut supports logging via the `log.Logger` interface and the\n`chestnut.WithLogger()` option. The `log.Logger` interface conforms to\n[Logrus](https://github.com/Sirupsen/logrus),\n[Zap](https://github.com/uber-go/zap), and the standard Go logger (with the\n`log.Std` adapter), for example:\n\n```go\nopt := chestnut.WithLogger(myLogger)\n```\n\n#### Logrus Logger\n\nChestnut supports [Logrus](https://github.com/Sirupsen/logrus) logger.\n\n`*logrus.Logger` and `*logrus.Entry` both work with Chestnut's `log.Logger` \ninterface:\n\n```go\nlogger := logrus.New() // *logrus.Logger\n\nopt := chestnut.WithLogger(logger)\n```\n\nor \n\n```go\nlogger := logrus.New()\nlogger = logger.WithField(\"hello\", \"world\") // *logrus.Entry\n\nopt := chestnut.WithLogger(logger)\n```\n\nIn addition to the `chestnut.WithLogger()` option, you can use the convenience \noption, `chestnut.WithLogrusLogger()`:\n\n```go\nopt := chestnut.WithLogrusLogger(log.InfoLevel)\n``` \n\n`chestnut.WithLogrusLogger()` will return a new `*logrus.Entry` set to the\nnormalized log level you requested. This is equivalent to calling `logrus.New()`\nfollowed by `logrus.SetLevel()`.\n\n#### Zap Logger\n\nChestnut supports [Zap](https://github.com/uber-go/zap) logger.\n\n`*zap.SugaredLogger` works with Chestnut's `log.Logger` interface:\n\n```go\nlogger :=zap.NewProduction().Sugar() // *zap.SugaredLogger\n\nopt := chestnut.WithLogger(logger)\n```\n\nIn addition to the `chestnut.WithLogger()` option, you can use the convenience\noption, `chestnut.WithZapLogger()`:\n\n```go\nopt := chestnut.WithZapLogger(log.InfoLevel)\n```\n\n`chestnut.WithZapLogger()` will return a new `*zap.SugaredLogger` set to the\nnormalized log level you requested. This is equivalent to calling \n`zap.NewProduction()`, followed by `zap.Core().Enabled()`, and finally, \n`zap.Sugar()`.\n\n#### Standard Logger\n\nChestnut supports the [Go standard](https://golang.org/pkg/log/) logger.\n\nWe provide a lightweight wrapper for Go's standard logger `*log.Logger` which\nsupports Chestnut's `log.Logger` interface.\n\n```go\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/jrapoport/chestnut\"\n    cnlog \"github.com/jrapoport/chestnut/log\"\n)\n\nlogger := cnlog.NewStdLogger(log.InfoLevel os.Stderr, \"\", log.LstdFlags)\nopt := chestnut.WithLogger(logger)\n```\n\n`chestnut.WithStdLogger()` will return a new `*log.stdLogger` set to the\nnormalized log level you requested. This is equivalent to calling\n`log.NewStdLogger()` with the specified log level.\n\n#### Storage\n\nLastly, the Chestnut stores also accept matching options for logging: \n`storage.WithLogger`, `storage.WithLogrusLogger`,  `storage.WithZapLogger`, and\n`storage.WithStdLogger`. \n\n```go\n// enable logging \nopt := storage.WithLogger(myLogger)\n// use nutsdb\nstore := nuts.NewStore(path, opt)\n```\n\nEnabling logging for the backing store was intentionally kept separate for \nadditional flexibility. This allows you to log Chestnut operations without\nautomatically incurring the noise of store operations and vice versa.\n\n## Examples\n\nRun any example with `make \u003cexample-dir\u003e`\n\n```shell\n$ make sparse\n```\n\n## Known Issues\n* Because we use JSON encoding for structs, time.Time will lose resolution when \n  encoded:\n  \n  ````\n  IN:  2021-01-09 17:29:36.349522 -0800 PST m=+0.002746093\n  OUT: 2021-01-09 17:29:36.349522 -0800 PST\n\n## Misc\n\n### JSON encoding\nWe use the [jsoniter](https://github.com/json-iterator/go) JSON encoder \ninternally. \n","funding_links":["https://www.buymeacoffee.com/jrapoport"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjrapoport%2Fchestnut","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjrapoport%2Fchestnut","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjrapoport%2Fchestnut/lists"}