coze

package module
v0.0.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 10, 2023 License: BSD-3-Clause Imports: 20 Imported by: 4

README

pkg.go.dev

⚠️ COZE IS IN ALPHA. USE AT YOUR OWN RISK ⚠️

Coze

Presentation

Coze

Coze is a cryptographic JSON messaging specification designed for human readability.

Try Coze out!

Example Coze

{
	"pay": {
		"msg": "Coze Rocks",
		"alg": "ES256",
		"iat": 1623132000,
		"tmb": "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
		"typ": "cyphr.me/msg"
	},
	"sig": "Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_w"
}

Coze Design Goals

  1. Valid and idiomatic JSON.
  2. Human readable and writable.
  3. Small in scope.
  4. Provide defined cipher suites.

Coze Fields

Coze defines standard fields and applications may include additional fields as desired. Coze JSON fields are case sensitive and unique. All fields are optional, but omitting standard fields may limit compatibility. Binary values are encoded as RFC 4648 base 64 URI canonical with padding truncated (b64ut). The Coze objects pay, key, and coze have respective standard fields. Unmarshalling JSON with duplicate fields must error.

All Coze Standard Fields

Coze Standard Fields

Pay

pay contains the fields alg, iat, tmb, and typ and optionally any additional application fields. In the first example msg is additional.

pay Standard Fields

  • alg - Specific cryptographic algorithm. E.g. "ES256"
  • iat - Time of message signature. E.g. 1623132000
  • tmb - Thumbprint of the signature's key.. E.g. "cLj8vs..."
  • typ - Type of pay.

typ's value may be used by applications as desired. The value is recommended to denote API information such as versioning, expected fields, and/or other application defined programmatic functions. In the first example, "typ":"cyphr.me/msg" denotes a pay with the fields ["msg","alg","iat","tmb","typ"] as defined by the application.

Coze Key

Example Public Coze Key

{
	"alg":"ES256",
	"iat":1623132000,
	"kid":"Zami's Majuscule Key.",
	"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
	"x":"2nTOaFVm2QLxmUO_SjgyscVHBtvHEfo2rq65MvgNRjORojq39Haq9rXNxvXxwba_Xj0F5vZibJR3isBdOWbo5g"
}

Example Private Coze Key

{
	"alg":"ES256",
	"iat":1623132000,
	"kid":"Zami's Majuscule Key.",
	"d":"bNstg4_H3m3SlROufwRSEgibLrBuRq9114OvdapcpVA",
	"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
	"x":"2nTOaFVm2QLxmUO_SjgyscVHBtvHEfo2rq65MvgNRjORojq39Haq9rXNxvXxwba_Xj0F5vZibJR3isBdOWbo5g"
}

key Standard Fields

  • key - Key object. E.g. "key":{"alg":"ES256", ...}
  • alg - Algorithm. E.g. "ES256"
  • d - Private component. E.g. "bNstg4..."
  • iat - "Issued at", When the key was created. E.g. 1623132000
  • kid - "Key identifier", Human readable, non-programmatic label. E.g. "kid":"My Cyphr.me Key".
  • tmb - Thumbprint. E.g. "cLj8vs..."
  • x - Public component. E.g. "2nTOaF...".
  • typ - "Type", Additional application information. E.g. "cyphr.me/msg"
  • rvk - "Revoke", time of key revocation. See the rvk section. E.g. 1623132000

Note that the private component d is not included in tmb generation. Also note that kid must not be used programmatically while typ may be used programmatically.

Coze object

The JSON name coze may be used to wrap Coze objects.

{
	"coze":{
		"pay": {
			"msg": "Coze Rocks",
			"alg": "ES256",
			"iat": 1623132000,
			"tmb": "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
			"typ": "cyphr.me/msg"
		},
		"sig": "Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_w"
	}
}

coze Standard Fields

  • coze (Coze) Coze object. E.g. {"coze":{"pay":..., sig:...}}
  • can (Canon) Canon of pay. E.g. ["alg","iat","tmb","typ"]
  • cad (Canon digest) Digest of pay. E.g. "LSgWE4v..."
  • czd (Coze digest) Digest of ["cad","sig"]. E.g. d0ygwQ...
  • pay (Payload) Pay object. E.g. "pay":{"alg":...}
  • sig (Signature) Signature over cad. E.g. "sig":"ywctP6..."

sig is the signature over the bytes of cad. cad is not rehashed before signing. czd's hashing algorithm must align with alg in pay. czd refers to a particular signed message just as cad refers to a particular payload. cad and czd are calculated from brace to brace, including the braces. cad and czd are recalculatable and are recommended to be omitted from cozies, although they may be useful for reference.

As an added technical constraint, because sig and czd are used as identifiers, sig must be non-malleable. Malleable schemes like ECDSA must perform signature canonicalization that constrains signatures to a non-malleable form.

Verbose coze

Including unneeded labels is not recommended. For example, the JSON object {"pay":{...},"sig":...} doesn't need the label coze if implicitly known by applications. The following includes fields that should generally be omitted. key may be looked up by applications by using tmb, can, cad, and czd are recalculatable, and the label coze may be inferred.

A tautologic coze:

{
	"coze": {
		"pay": {
			"msg": "Coze Rocks",
			"alg": "ES256",
			"iat": 1623132000,
			"tmb": "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
			"typ": "cyphr.me/msg"
		},
		"key": {
			"alg":"ES256",
			"iat":1623132000,
			"kid":"Zami's Majuscule Key.",
			"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
			"x":"2nTOaFVm2QLxmUO_SjgyscVHBtvHEfo2rq65MvgNRjORojq39Haq9rXNxvXxwba_Xj0F5vZibJR3isBdOWbo5g"
		},
		"can": ["msg","alg","iat","tmb","typ"],
		"cad": "Ie3xL77AsiCcb4r0pbnZJqMcfSBqg5Lk0npNJyJ9BC4",
		"czd": "TnRe4DRuGJlw280u3pGhMDOIYM7ii7J8_PhNuSScsIU",
		"sig": "Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_w"
	}
}

Simplified:

{
	"pay": {
		"msg": "Coze Rocks",
		"alg": "ES256",
		"iat": 1623132000,
		"tmb": "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
		"typ": "cyphr.me/msg"
	},
	"sig": "Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_w"
}

Canon

A canon is a list of fields used for normalization, e.g. ["alg","x"]. Coze objects are canonicalized for creating digests, signing, and verification. The canon of pay is the currently present fields in order of appearance. The following Coze fields have predefined canons:

  • cad's canon is pay's canon.
  • tmb's canon is ["alg","x"].
  • czd's canon is ["cad","sig"].

Using a canon, the canonical form of an object is generated by removing fields not appearing in the canon, ordering remaining fields by appearance in the canon, and eliding unnecessary whitespace. Note that's since pay's canon is the present fields, no fields are removed when canonicalizing pay.

Generation steps for the canonical form:

  • Omit fields not present in canon.
  • Order fields by canon.
  • Omit insignificant whitespace.

A canonical digest is generated by hashing the canonical form using the hashing algorithm specified by alg. For example,"ES256"'s hashing algorithm is "SHA-256".

The key thumbprint, tmb, is the canonical digest of key using the canon ["alg","x"] and hashing algorithm specified by key.alg. For example, a key alg of ES256 corresponds to the hashing algorithm SHA-256. The canonical form of the example key is:

{"alg":"ES256","x":"2nTOaFVm2QLxmUO_SjgyscVHBtvHEfo2rq65MvgNRjORojq39Haq9rXNxvXxwba_Xj0F5vZibJR3isBdOWbo5g"}

Hashing this canonical form results in the following digest, which is tmb: cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk.

czd is the canonical digest of coze with the canon ["cad","sig"], which results in the JSON {"cad":"...",sig:"..."}. czd's hash must align with alg in pay.

The canonical digest of

  • pay is cad,
  • ["alg","x"] is tmb,
  • ["cad","sig"] is czd.

Using the first example, the following canonical digests are calculated:

  • tmb is cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk
  • cad is LSgWE4vEfyxJZUTFaRaB2JdEclORdZcm4UVH9D8vVto.
  • czd is d0ygwQCGzuxqgUq1KsuAtJ8IBu0mkgAcKpUJzuX075M.

Signing and verification functions must not mutate pay. Any mutation of pay via can must occur by canon related functions.

Coze and Binaries

The canonical digest of a binary file may simply be the digest of the file. The hashing algorithm and any other metadata may be denoted by an accompanying coze. For example, an image ("coze_logo_icon_256.png") may be referred to by its digest.

{
	"alg":"SHA-256",
	"file_name":"coze_logo_icon_256.png",
	"id":"oDBDAg4xplHQby6iQ2lZMS1Jz4Op0bNoD5LK3KxEUZo"
}

For example, a file's digest, denoted by id, may represent the authorization to upload a file to a user's account.

{
 "pay": {
  "alg": "ES256",
  "file_name": "coze_logo_icon_256.png",
  "id": "oDBDAg4xplHQby6iQ2lZMS1Jz4Op0bNoD5LK3KxEUZo",
  "iat": 1623132000,
  "tmb": "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
  "typ": "cyphr.me/file/create"
 },
 "sig": "DgJb6Qb81uhC-ulZJlIIj8ahi0b5rAbtnkQhiEH1FB0HeNiACVh_Deo6a22OkK2tr0UcDOiIRY1X-BUriw03Mg"
}

Revoke

A Coze key may be revoked by signing a self-revoke coze. A self-revoke coze has the field rvk with an integer value greater than 0. For example, the integer value 1 is suitable to denote revocation. A Unix timestamp of revocation is the suggested value for rvk.

Example Self Revoke

{
 "pay": {
  "alg": "ES256",
  "iat": 1623132000,
  "msg": "Posted my private key online",
  "rvk": 1623132000,
  "tmb": "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
  "typ": "cyphr.me/key/revoke"
 },
 "sig": "KVjPjMVHoL828WyAH5biqIOt-IOaQ5EBtN_7eQifP2w3agUHu6KfqO40_oqQ5GE_BShgXvhbK0O6Z2h5YPNAcw"
}
  • rvk - Unix timestamp of when the key was revoked.

Coze explicitly defines a self-revoke method so that third parties may revoke leaked keys. Systems storing Coze keys should provide an interface permitting a given Coze key to be marked as revoked by receiving a self-revoke message. Self-revokes with future times must immediately be considered as revoked.

rvk must and iat should be a positive integer less than 2^53 – 1 (9,007,199,254,740,991), which is the integer precision limit specified by IEEE754 minus one. Revoke checks must error if rvk's is not an integer or larger than 2^53 - 1.

Key expiration policies, key rotation, and alternative revocation methods are outside the scope of Coze.

Alg

alg specifies a parameter set and is a single source of truth for Coze cryptographic operations.

Example - "alg":"ES256"

  • Name: ES256
  • Genus: ECDSA
  • Family: EC
  • Use: sig
  • Hash: SHA-256
  • HashSize: 32
  • HashSizeB64: 43
  • XSize: 64
  • XSize64: 86
  • DSize: 32
  • DSize64: 43
  • Curve: P-256
  • SigSize: 64
  • SigSize64: 86

Supported Algorithms

  • ES224
  • ES256
  • ES384
  • ES512
  • Ed25519
  • Ed25519ph (planned)

Coze Verifier

The Coze verifier is an in-browser tool for signing and verifying.

Coze Verifier

coze_verifier

There is also the Simple Coze Verifier that has the minimal amount of code needed for a basic Coze application.
Its codebase is in the Cozejs repo and may be locally hosted.

CozeCLI

See the Coze CLI repository.

Coze Implementations

See docs/development.md for the Go development guide.

Coze Core and Coze X

The sections above are defined as the main Coze specification, Coze core. There are no plans to increase Coze's scope or features in core other than additional algorithm support. This will be especially true after Coze is out of Alpha/Beta. (At the moment, we would like more time for feedback before casting the specification into stone.)

Coze x (Coze extended) includes additional documentation, extra features, drafts, proposals, early new algorithms support that's not yet adopted in Coze core, and extended algorithm support.

See Coze_go_x/normal for an example of a Coze x feature not included in Coze core.

Repository structure:

  • Coze Main specification (core) and the Go Coze reference implementation.
  • CozeX Coze extended. Additional documents, discussion, and new algorithms (Not a code repository).
  • CozeGoX Go implementation of extended features.
  • CozeJS Javascript implementation of Coze core.
  • CozeJSX Javascript implementation of extended.
  • etc...
FAQ

Pronunciation? What does "Coze" mean?

We say "Co-zee" like a comfy cozy couch. Jared suggested Coze because it's funny. The English word Coze is pronounced "kohz" and means "a friendly talk; a chat" which is the perfect name for a messaging standard.

"Coze" vs "coze"?

We use upper case "Coze" to refer to the specification, and "coze" to refer to coze messages and objects.

Why release pre-alpha on 2021/06/08?

Coze was released on 2021/06/08 (1623132000) since it's 30 years and one day after the initial release of PGP 1.0.

ASCII/Unicode/UTF-8/UTF-16 and Ordering?

Although JSON was designed in a Javascript (UTF-16) context, the latest JSON RFC requires UTF-8 encoding, a decision that was made because all significant existing implementations used UTF-8. Unicode is a superset of ASCII and UTF-8 shares sorting order with Unicode. This results in broad, out of the box compatibility. Object field order may be denoted by a canon, chaining normal, or communicate ordering via other means. If applications need sorted fields, UTF-8/Unicode order is suggested. Note that UTF-16 (Javascript) has some code points out of order and for those systems a small amount of additional logic is needed to correct ordering.

Binary? Why not support binary payloads?

JSON isn't well designed for large binary payloads. Instead, Coze suggests including the digest of a binary file in a coze message while transporting the binary separately. There's nothing stopping an application from base 64 encoding a binary for transport, although it's not recommended.

Why is Coze's scope so limited?

Coze is intentionally scope limited. It is easier to extend a limited standard than to fix a large standard. Coze can be extended and customized for individual applications.

Is Coze versioned?

alg refers to a specific set of parameters for all operations. If an operation needs a different parameter set, alg itself must denote the difference.

Implementation releases themselves are versioned. The hope for the specification "Coze Core" stays simple and stable enough to preclude versioning. Instead, Coze Core "versioning" will be accomplished by noting specific algorithm support. Versioning by feature permits Coze implementations to support a subset of features while remaining Coze compliant. In this way libraries may remain spartan and avoid feature bloat. Further expansions on Coze may be included in "Coze Standard". Further draft, proposals, and extended algorithm support are planned in "Coze Experimental".

Why does pay have cryptographic components?

Coze's pay includes all payload information, a design we've dubbed a "fat payload". We consider single pass hashing critical for Coze's simple design.

Alternative schemes require a larger canon, {"head":{...},"pay":{...}}, or concatenation like digest(head) || digest(pay). By hashing only pay, the "head" label and encapsulating braces are dropped, pay:{...}, and the label "pay" may then be inferred, {...}. {...} is better than {"head":{...},"pay":{...}}.

Verifying a coze already requires hashing pay. Parsing alg from pay is a small additional cost.

JSON APIs? Can my API do versioning?

Coze is well suited for JSON APIs. API versioning may be handled by applications however desired. A suggested way of incorporating API versioning in Coze is to use typ, e.g. "typ":"cyphr.me/v1/msg/create", where "v1" is the api version.

Can my application use Canon/Canonicalization?

Yes, canon is suitable for general purpose application. Applications may specify canon expectations in API documentation, if using Coze denoted by "typ" or explicitly specified by can, or implicitly known and pre-established. Coze Core contains simple canonicalization functions, or for more expressive capabilities see Normal.

pay.typ vs key.typ.

For applications, pay.typ may denote a canon. For example, a typ with value cyphr.me/msg/create has a canon, as defined by the service, of ["alg", "iat", "msg", "tmb", "typ"]. The service may reject a coze that's not canonicalized as expected. For example, the service might reject cozies missing iat.

Key.tmb ignores key.typ because a static canon, ["alg","x"] is always used when producing key's tmb. Like typ in pay, applications may use key.typ to specify custom fields, e.g. "first_seen" or "account_id" and field order.

ECDSA x and sig Bytes.

For ECDSA , (X and Y) and (R and S) are concatenated for x and sig respectively. For ES512, which unlike the other ECDSA algorithms uses the odd numbered P-521, X, Y, R, and S are padded before concatenation.

Why use tmb and not x for references in messages?

Coze places no limit on public key size which may be large. For example, GeMSS128 public keys are 352,188 bytes, compared to Ed25519's 32 bytes. Using tmb instead of x generalizes Coze for present and future algorithm use. Additionally, x may be cryptographically significant for key security while tmb is not.

Required Coze Fields, Contextual Cozies, and the Empty Coze.

The standard fields provide Coze and applications fields with known types since JSON has limited type identifiers. Coze has no required fields, however omitting standard fields limits interoperability among applications, so it is suggested to include standard fields appropriately.

Cozies that are missing the fields pay.alg and/or pay.tmb are contextual cozies, denoting that additional information is needed for verification. Caution is urged when deploying contextual cozies as including the standard fields pay.alg and pay.tmb is preferred.

An empty coze, which has an empty pay and populated sig, is legitimate. It may be verified if key is known. The following empty coze was signed with the example key "cLj8vs".

{
	"pay":{},
	"sig":"9iesKUSV7L1-xz5yd3A94vCkKLmdOAnrcPXTU3_qeKSuk4RMG7Qz0KyubpATy0XA_fXrcdaxJTvXg6saaQQcVQ"
}

UTF-8 and B64UT (RFC Base 64 URI canonical Truncated) Encoding

Canonical base 64 (sometimes called "strict") encoding is required and non-strict encoding of both b64ut and UTF-8 must error. For the initial reason for why Coze uses b64ut see base64.md.

Why not PGP/OpenSSL/LibreSSL/SSHSIG/libsodium/JOSE(JWT)/COSE/etc...?

We respect the various projects in the space. Other projects have noble goals and we're thankful they exist. Coze is influenced by ideas from many others. However existing solutions were not meeting our particular needs so we created Coze.

See coze_vs.md and the introduction presentation for more.

Does Coze have checksums?

x, tmb,cad, czd, and sig may be used for integrity checking.

Systems may use sig as an integrity check via cryptographic verification. If cad and/or czd are included they may be recalculated and error on mismatch.

For keys, x and/or tmb may be recalculated and error on mismatch.Coze keys cannot be integrity checked when d, x, or tmb are presented alone. In situations needing integrity checking, we recommend including at least two components. See checksums.md for more.

Performance hacks?

Coze is not optimized for long messages, but if early knowledge of Coze standard fields is critical for application performance, put the Coze standard fields first, e.g. {"alg", "tmb", ...}

I need to keep my JSON separate but inside a coze.

If appending custom fields after the standard Coze fields isn't sufficient, we suggest encapsulating custom JSON in "~", the last ASCII character. We've dubbed this a "tilde encapsulated payload". For example:

{
	"alg": "ES256",
	"iat": 1623132000,
	"tmb": "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
	"typ": "cyphr.me/msg/create",
	"~": {
		"msg": "tilde encapsulated payload"
	}
}

Where does the cryptography come from?

Much of this comes from NIST FIPS.

For example, FIPS PUB 186-3 defines P-224, P-256, P-384, and P-521.

To learn more see this walkthrough of ECDSA.

Unsupported Things?

The following are out of scope or redundant.

  • ES192, P-192 - Not implemented anywhere and dropped from later FIPS.
  • SHA1, MD5 - Not considered secure for a long time.
  • kty - "Key type". Redundant by alg.
  • iss - tmb fulfills this role. Systems that need something like an issuer, associating messages with people/systems, can look up "issuer" based on thumbprint. Associating thumbprints to issuers is the design we recommend.
  • exp - "Expiration". Outside the scope of Coze.
  • nbf - "Not before". Outside the scope of Coze.
  • aud - "Audience". Outside the scope of Coze, but consider denoting this with typ.
  • sub - "Subject". Outside the scope of Coze, but consider denoting this with typ.
  • jti - "Token ID/JWT ID". Redundant by czd, cad, or an application specified field.

Encryption?

Coze does not currently support encryption. If or when it ever does it would be similar to or simply complement age.

Why define algorithms?

It’s not enough to implement a single standard; it’s vital that our systems be able to easily swap in new algorithms when required. We’ve learned the hard way how algorithms can get so entrenched in systems that it can take many years to update them: in the transition from DES to AES, and the transition from MD4 and MD5 to SHA, SHA-1, and then SHA-3.

-- Bruce Schneier

Coze's design is generalized and not overly coupled to any single primitive. Because of this, applications that use Coze can easy upgrade cryptographic primitives. Using a single primitive is perfectly fine, but tightly coupling systems to a single primitive is not. Simultaneous support for multiple primitives is a secondary, and optional, perk.

JSON "Name", "Key", "Field Name", "Member Name"?

They're all synonyms. A JSON name is a JSON key is a JSON field name is a JSON member name. In this document we use "field name" to avoid confusion with Coze key.

Why are duplicate field names prohibited?

Coze explicitly requires that implementations disallow duplicate field names in coze, pay, and key. Existing JSON implementations have varying behavior.
Douglas Crockford, JSON's inventor, tried to fix this but it was decided it was too late.

Although Douglas Crockford couldn't change the spec forcing all implementations to error on duplicate, his Java JSON implementation errors on duplicate names. Others use last-value-wins, support duplicate keys, or other non-standard behavior. The JSON RFC states that implementations should not allow duplicate keys, notes the varying behavior of existing implementations, and states that when names are not unique, "the behavior of software that receives such an object is unpredictable." Also note that Javascript objects (ES6) and Go structs already require unique names.

Duplicate fields are a security issue, a source of bugs, and a surprising behavior to users. See the article, "An Exploration of JSON Interoperability Vulnerabilities"

Disallowing duplicates conforms to the small I-JSON RFC. The author of I-JSON, Tim Bray, is also the author of JSON RFC 8259. See also https://github.com/json5/json5-spec/issues/38.

JSON?

See also I-JSON and JSON5

HTTP? HTTP Cookies? HTTP Headers?

When using Coze with HTTP cookies, Coze messages should be JSON minified. For example, we've encountered no issues using the first example as a cookie:

token={"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"sig":"Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_w"}; Path=/;  Secure; Max-Age=999999999; SameSite=None

For more considerations see http_headers.md

Signature Malleability?

Coze prohibits signature malleability. See malleability_low_s.md.

Who created Coze?

Coze was created by Cyphr.me.

Discussion? Social Media?

Other Resources


Attribution, Trademark Notice, and License

Coze is released under The 3-Clause BSD License.

"Cyphr.me" is a trademark of Cypherpunk, LLC. The Cyphr.me logo is all rights reserved Cypherpunk, LLC and may not be used without permission.

Documentation

Overview

See the README on https://github.com/Cyphrme/Coze

This library exports some functions that may be helpful for other applications, but should not be considered apart of the Coze specification API.

  • ECDSAToLowSSig
  • IsLowS
  • ToLowS
  • Marshal
  • MarshalPretty

Other auxiliary types and functions, like B64, are reasonable exports for package Coze, especially since compatible implementations are absent from the Go standard library.

Example (Iat_rvk_too_big)

Demonstrates expectations for values that are non-integer, negative, or too large for rvk.

p := &Pay{}

//  2^53 - 1 as a string which must error.
err := json.Unmarshal([]byte(`{"rvk":"9007199254740991"}`), p)
if err != nil {
	fmt.Println(err)
}

// 2^53
err = json.Unmarshal([]byte(`{"rvk":9007199254740992}`), p)
if err != nil {
	fmt.Println(err)
}

//  Negative 2^53 + 1 must error as rvk must be positive.
err = json.Unmarshal([]byte(`{"rvk":-9007199254740991}`), p)
if err != nil {
	fmt.Println(err)
}

// Finally, 2^53 - 1 as an integer which is okay.
err = json.Unmarshal([]byte(`{"rvk":9007199254740991}`), p)
if err != nil {
	fmt.Println(err)
}
fmt.Println(p)
Output:

json: cannot unmarshal string into Go struct field pay2.rvk of type int64
Pay.UnmarshalJSON: values for iat and rvk must be between 0 and 2^53 - 1
Pay.UnmarshalJSON: values for iat and rvk must be between 0 and 2^53 - 1
{"rvk":9007199254740991}

Index

Examples

Constants

View Source
const (
	UnknownAlg    Alg    = "UnknownAlg"
	UnknownSigAlg SigAlg = "UnknownSigAlg"
	ES224         SigAlg = "ES224"
	ES256         SigAlg = "ES256"
	ES384         SigAlg = "ES384"
	ES512         SigAlg = "ES512"
	Ed25519       SigAlg = "Ed25519"
	Ed25519ph     SigAlg = "Ed25519ph"
	Ed448         SigAlg = "Ed448"
	UnknownEncAlg EncAlg = "UnknownEncAlg"
	UnknownHshAlg HshAlg = "UnknownHshAlg"
	SHA224        HshAlg = "SHA-224"
	SHA256        HshAlg = "SHA-256"
	SHA384        HshAlg = "SHA-384"
	SHA512        HshAlg = "SHA-512"
	SHA3224       HshAlg = "SHA3-224"
	SHA3256       HshAlg = "SHA3-256"
	SHA3384       HshAlg = "SHA3-384"
	SHA3512       HshAlg = "SHA3-512"
	SHAKE128      HshAlg = "SHAKE128"
	SHAKE256      HshAlg = "SHAKE256"
)

Variables

Algs includes all algs, including unknown algs, SigAlg, EncAlg, and HshAlg.

View Source
var CzdCanon = []string{"cad", "sig"}

CzdCanon is the canon for a `czd`.

View Source
var EncAlgs = []EncAlg{
	UnknownEncAlg,
}

Encryption algs.

Hash algs.

View Source
var KeyCanon = []string{"alg", "x"}

KeyCanonSlice is the canonical form of a Coze key in slice form.

Functions

func Canon

func Canon(raw json.RawMessage) (can []string, err error)

Canon returns the current canon from raw JSON.

It returns only top level fields with no recursion or promotion of embedded fields.

Example
b := []byte(`{"z":"z", "a":"a"}`)

can, err := Canon(b)
if err != nil {
	panic(err)
}

fmt.Println(can)
Output:

[z a]

func Canonical

func Canonical(input []byte, canon any) (b []byte, err error)

Canonical returns the canonical form. Input canon is optional and may be nil. If canon is nil, input JSON is only compactified.

Interface "canon" may be `[]string`, `struct“, or `nil`. If "canon" is a struct or slice it must be properly ordered. If canon is nil, json.Unmarshal will place the input into a UTF-8 ordered map.

In the Go version of Coze, the canonical form of a struct is (currently) achieved by unmarshalling and remarshalling.

Example

ExampleCanonical.

var b []byte
var err error

type ABC struct {
	A string `json:"a"`
	B string `json:"b,omitempty"`
	C string `json:"c"`
}

// []byte (out of order) with nil canon.  Missing field  "b" should be omitted
// from output.
ca, err := Marshal(map[string]string{"c": "c", "a": "a"})
if err != nil {
	panic(err)
}

b, err = Canonical(ca, nil)
if err != nil {
	panic(err)
}
fmt.Printf("Input: []byte; Canon: nil    => %s\n", b)

// []byte (out of order) with struct (in order) canon. Missing field "b"
// should be omitted from output.
ca, err = Marshal(map[string]string{"c": "c", "a": "a"})
if err != nil {
	panic(err)
}
b, err = Canonical(ca, new(ABC))
if err != nil {
	panic(err)
}
fmt.Printf("Input: []byte; Canon: struct => %s\n", b)

// []byte (out of order) with struct (in order) canon.
byteJSON := []byte(`{"c":"c", "a": "a"}`)
b, err = Canonical(byteJSON, nil)
if err != nil {
	panic(err)
}
fmt.Printf("Input: []byte; Canon: nil    => %s\n", b)
Output:

Input: []byte; Canon: nil    => {"a":"a","c":"c"}
Input: []byte; Canon: struct => {"a":"a","c":"c"}
Input: []byte; Canon: nil    => {"c":"c","a":"a"}
Example (Slice)
dig, err := CanonicalHash([]byte(GoldenKeyString), KeyCanon, GoldenKey.Alg.Hash())
if err != nil {
	panic(err)
}
fmt.Println(dig)
Output:

cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk
Example (Struct)

ExampleCanonical_struct demonstrates using a given struct as a canon.

// KeyCanon is the canonical form of a Coze key in struct form.
type KeyCanonStruct struct {
	Alg string `json:"alg"`
	X   B64    `json:"x"`
}
kcs := new(KeyCanonStruct)

dig, err := CanonicalHash([]byte(GoldenKeyString), kcs, GoldenKey.Alg.Hash())
if err != nil {
	panic(err)
}
fmt.Println(dig)
Output:

cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk

func ECDSAToLowSSig added in v0.0.4

func ECDSAToLowSSig(c *Key, coze *Coze) (err error)

ECDSAToLowSSig generates low-S signature from existing ecdsa signatures (high or low-S). This is useful for migrating signatures from non-Coze systems that may have high S signatures. See Coze docs on low-S.

Example

Example_ECDSAToLowSSig demonstrates converting non-coze compliant high S signatures to the canonicalized, coze compliant low-S form.

highSCozies := []string{
	`{"pay":{},"sig":"9iesKUSV7L1-xz5yd3A94vCkKLmdOAnrcPXTU3_qeKSuk4RMG7Qz0KyubpATy0XA_fXrcdaxJTvXg6saaQQcVQ"}`,
	`{"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"sig":"mVw8N6ZncWcObVGvnwUMRIC6m2fbX3Sr1LlHMbj_tZ3ji1rNL-00pVaB12_fmlK3d_BVDipNQUsaRyIlGJudtg"}`,
	`{"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"sig":"cn6KNl4VQlk5MzmhYFVyyJoTOU57O5Bq-8r-yXXR6Ojfs0-6LFGd8j1Y6wiJAQrGpWj_RptsiEg49v95FsVWMQ"}`,
	`{"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"sig":"9KvWfOSIZUjW8Ie0jbdVdu9UlIP4TT4MXz3YyNW3fCTWXHnO1MPROwcXvfNZN_icOvMAK3vfsr2w-CeBozS81w"}`,
}

for _, s := range highSCozies {
	cz := new(Coze)
	err := json.Unmarshal([]byte(s), cz)
	if err != nil {
		panic(err)
	}
	v, _ := GoldenKey.VerifyCoze(cz)
	if v {
		panic("High S coze should not validate.")
	}

	err = ECDSAToLowSSig(&GoldenKey, cz)
	if err != nil {
		panic(err)
	}

	v, _ = GoldenKey.VerifyCoze(cz)
	if !v {
		panic("low-S coze should validate.")
	}

	fmt.Printf("%s\n", cz)
}
Output:

{"pay":{},"sig":"9iesKUSV7L1-xz5yd3A94vCkKLmdOAnrcPXTU3_qeKRRbHuy5EvMMFNRkW_sNLo-vvEPO9BmeUkcNh-ok18I_A"}
{"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"sig":"mVw8N6ZncWcObVGvnwUMRIC6m2fbX3Sr1LlHMbj_tZ0cdKUx0BLLW6l-KJAgZa1IRPaln3zKXTnZcqid48eHmw"}
{"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"sig":"cn6KNl4VQlk5MzmhYFVyyJoTOU57O5Bq-8r-yXXR6OggTLBE065iDsKnFPd2_vU5F337ZwurFjy6wstJ5Z3PIA"}
{"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"sig":"9KvWfOSIZUjW8Ie0jbdVdu9UlIP4TT4MXz3YyNW3fCQpo4YwKzwuxfjoQgymyAdjgfP6gis368dCwaNBWS5oeg"}

func IsLowS added in v0.0.4

func IsLowS(c *Key, s *big.Int) (bool, error)

IsLowS checks if S is a low-S for ECDSA. See Coze docs on low-S.

func Marshal

func Marshal(i any) ([]byte, error)

Marshal is a JSON friendly marshaller. json.Marshal preemptively replaces the valid JSON and UTF-8 characters "&". "<", ">" with the "slash u" unicode escapes (e.g. \u0026) in the name of HTML friendliness, for example: https://play.golang.org/p/o2hiX0c62oN. As JSON is not HTML, preemptive HTML escaping is incorrect. (The JSON spec calls for no such measures and other industry encoders do no such preemptive escaping.) Where JSON may include these legitimate characters, like sanitized arbitrary user data, json.Marshal should not be used. The Go team is aware that the existing implementation has this and other concerns but has not yet released a new version. Joe Tsai is working on fixes in a yet-to-be-released Tailscale's "JSONv2" package, which we hope to use upon release. https://pkg.go.dev/github.com/go-json-experiment/json. The package also enumerates other JSON best practices that may be of concern for anyone concerned with the aforementioned issue. https://github.com/go-json-experiment/json#behavior-changes. See https://github.com/Cyphrme/Coze/issues/15 for other JSON encoding concerns.

Go structs already require unique fields, so unlike coze.UnmarshalJSON or pay.UnmarshalJSON, marshaling will not sanitize for duplicates.

Example (JsonRawMessage)

ExampleMarshal_jsonRawMessage demonstrates using empty string, two quote characters, and nil for json.RawMessage. When using json.RawMessage, it should always be valid JSON or nil or otherwise it will result in an error.

o := json.RawMessage([]byte("")) // empty string
anon := struct {
	Obj *json.RawMessage `json:"obj,omitempty"`
}{
	Obj: &o,
}

// Incorrect usage with pointer to a zero value string.
// Pointer to empty string will fail Marshal since an empty string is not
// valid JSON., while the value `""` will pass.
b, err := Marshal(anon)         // fails
fmt.Printf("%s\n%+v\n", err, b) // Error is populated and b is empty because of error.

// Correct usage with quotes characters.
quotes := []byte("\"\"") // string with two quote characters
anon.Obj = (*json.RawMessage)(&quotes)
b, err = Marshal(anon)
if err != nil {
	panic(err)
}
fmt.Printf("%s\n", b)

// Correct usage with with `nil`, which prints as the JSON "null".
o = nil
anon.Obj = &o
b, err = Marshal(anon)
if err != nil {
	panic(err)
}
fmt.Printf("%s\n", b)
Output:

json: error calling MarshalJSON for type *json.RawMessage: unexpected end of JSON input
[]
{"obj":""}
{"obj":null}

func MarshalPretty

func MarshalPretty(i any) ([]byte, error)

MarshalPretty uses 4 spaces for each level. Spaces are used instead of tabs because some applications display tabs as 8 spaces, which is excessive.

func ToLowS added in v0.0.4

func ToLowS(c *Key, s *big.Int) error

ToLowS converts high-S to low-S or if already low-S returns itself. It does this by (N - S) where N is the order. See Coze docs on low-S.

Types

type Alg added in v0.0.1

type Alg string // Alg is for all "SpcAlg" cryptographic algorithms.

Alg is a declarative abstraction for cryptographic functions for Coze. For more on Alg, see the main Coze README.

Hierarchy for signing and hashing cryptographic functions. Naming is inspired by taxonomic rank.

  • Level 0 species - "SpcAlg" (e.g.: ES256) (species)
  • Level 1 genus - "GenAlg" (e.g.: ECDSA) (genus)
  • Level 2 family - "FamAlg" (e.g.: EC) (family)

The value for a Coze `alg` is always a specific (species) algorithm, e.g. "ES256", and never any other rank, e.g. "ECDSA". The type `Alg` in this package may be any algorithm of any rank.

Cryptographic Signature/Encryption/Hashing hierarchy

  • EC
  • ECDSA
  • ES224
  • ES256
  • ES384
  • ES512
  • EdDSA
  • Ed25519
  • Ed25519ph
  • Ed448
  • SHA
  • SHA-2
  • SHA-224
  • SHA-256
  • SHA-384
  • SHA-512
  • SHA-3
  • SHA3-224
  • SHA3-256
  • SHA3-384
  • SHA3-512
  • SHAKE128
  • SHAKE256

"SE" (singing, encryption) is the super type of signing and encryption and excludes hashing.

See the main Coze README for Coze supported and unsupported things.

Example (JsonMarshal)
type algStruct struct {
	A Alg `json:"alg"`
}

b, err := Marshal(algStruct{A: Alg(ES256)})
if err != nil {
	panic(err)
}
fmt.Printf("%s\n", b)

type seAlgStruct struct {
	A SEAlg `json:"alg"`
}

b, err = Marshal(seAlgStruct{A: SEAlg(ES256)})
if err != nil {
	panic(err)
}
fmt.Printf("%s\n", b)
Output:

{"alg":"ES256"}
{"alg":"ES256"}

func Parse added in v0.0.1

func Parse(s string) Alg

func (Alg) Curve added in v0.0.1

func (a Alg) Curve() Crv

Curve returns the curve for the given alg. Returns empty if alg does not have a curve.

func (Alg) Family added in v0.0.1

func (a Alg) Family() FamAlg

Family is for EC, SHA, and RSA.

func (Alg) Genus added in v0.0.1

func (a Alg) Genus() GenAlg

Genus is for ECDSA, EdDSA, SHA-2, SHA-3.

func (Alg) Hash added in v0.0.1

func (a Alg) Hash() HshAlg

Hash returns respective hashing algorithm if specified. If alg is a hashing algorithm, it returns itself.

func (Alg) Params added in v0.0.1

func (a Alg) Params() Params

Params sets and returns a Params struct. See struct definition.

Example
algs := []Alg{
	Alg(ES224), Alg(ES256), Alg(ES384), Alg(ES512), Alg(Ed25519), Alg(Ed25519ph),
	Alg(Ed448), Alg(SHA224), Alg(SHA256), Alg(SHA384), Alg(SHA512), Alg(SHA3224),
	Alg(SHA3256), Alg(SHA3384), Alg(SHA3512), Alg(SHAKE128), Alg(SHAKE256),
}
fmt.Println(algs)

for _, a := range algs {
	params := a.Params()

	b, _ := Marshal(params)
	fmt.Printf("%s\n", b)
}
Output:

[ES224 ES256 ES384 ES512 Ed25519 Ed25519ph Ed448 SHA-224 SHA-256 SHA-384 SHA-512 SHA3-224 SHA3-256 SHA3-384 SHA3-512 SHAKE128 SHAKE256]
{"Name":"ES224","Genus":"ECDSA","Family":"EC","Use":"sig","Hash":"SHA-224","HashSize":28,"HashSizeB64":38,"XSize":56,"XSizeB64":75,"DSize":28,"DSizeB64":38,"Curve":"P-224","SigSize":56,"SigSizeB64":75}
{"Name":"ES256","Genus":"ECDSA","Family":"EC","Use":"sig","Hash":"SHA-256","HashSize":32,"HashSizeB64":43,"XSize":64,"XSizeB64":86,"DSize":32,"DSizeB64":43,"Curve":"P-256","SigSize":64,"SigSizeB64":86}
{"Name":"ES384","Genus":"ECDSA","Family":"EC","Use":"sig","Hash":"SHA-384","HashSize":48,"HashSizeB64":64,"XSize":96,"XSizeB64":128,"DSize":48,"DSizeB64":64,"Curve":"P-384","SigSize":96,"SigSizeB64":128}
{"Name":"ES512","Genus":"ECDSA","Family":"EC","Use":"sig","Hash":"SHA-512","HashSize":64,"HashSizeB64":86,"XSize":132,"XSizeB64":176,"DSize":66,"DSizeB64":88,"Curve":"P-521","SigSize":132,"SigSizeB64":176}
{"Name":"Ed25519","Genus":"EdDSA","Family":"EC","Use":"sig","Hash":"SHA-512","HashSize":64,"HashSizeB64":86,"XSize":32,"XSizeB64":43,"DSize":32,"DSizeB64":43,"Curve":"Curve25519","SigSize":64,"SigSizeB64":86}
{"Name":"Ed25519ph","Genus":"EdDSA","Family":"EC","Use":"sig","Hash":"SHA-512","HashSize":64,"HashSizeB64":86,"XSize":32,"XSizeB64":43,"DSize":32,"DSizeB64":43,"Curve":"Curve25519","SigSize":64,"SigSizeB64":86}
{"Name":"Ed448","Genus":"EdDSA","Family":"EC","Use":"sig","Hash":"SHAKE256","HashSize":64,"HashSizeB64":86,"XSize":57,"XSizeB64":76,"DSize":57,"DSizeB64":76,"Curve":"Curve448","SigSize":114,"SigSizeB64":152}
{"Name":"SHA-224","Genus":"SHA2","Family":"SHA","Use":"hsh","Hash":"SHA-224","HashSize":28,"HashSizeB64":38}
{"Name":"SHA-256","Genus":"SHA2","Family":"SHA","Use":"hsh","Hash":"SHA-256","HashSize":32,"HashSizeB64":43}
{"Name":"SHA-384","Genus":"SHA2","Family":"SHA","Use":"hsh","Hash":"SHA-384","HashSize":48,"HashSizeB64":64}
{"Name":"SHA-512","Genus":"SHA2","Family":"SHA","Use":"hsh","Hash":"SHA-512","HashSize":64,"HashSizeB64":86}
{"Name":"SHA3-224","Genus":"SHA3","Family":"SHA","Use":"hsh","Hash":"SHA3-224","HashSize":28,"HashSizeB64":38}
{"Name":"SHA3-256","Genus":"SHA3","Family":"SHA","Use":"hsh","Hash":"SHA3-256","HashSize":32,"HashSizeB64":43}
{"Name":"SHA3-384","Genus":"SHA3","Family":"SHA","Use":"hsh","Hash":"SHA3-384","HashSize":48,"HashSizeB64":64}
{"Name":"SHA3-512","Genus":"SHA3","Family":"SHA","Use":"hsh","Hash":"SHA3-512","HashSize":64,"HashSizeB64":86}
{"Name":"SHAKE128","Genus":"SHA3","Family":"SHA","Use":"hsh","Hash":"SHAKE128","HashSize":32,"HashSizeB64":43}
{"Name":"SHAKE256","Genus":"SHA3","Family":"SHA","Use":"hsh","Hash":"SHAKE256","HashSize":64,"HashSizeB64":86}

func (*Alg) Parse added in v0.0.1

func (a *Alg) Parse(s string)
Example
algs := []string{
	"",
	"foo",
	"UnknownAlg",
	"UnknownSigAlg",
	"ES224",
	"ES256",
	"ES384",
	"ES512",
	"Ed25519",
	"Ed25519ph",
	"Ed448",
	"UnknownEncAlg",
	"UnknownHshAlg",
	"SHA-224",
	"SHA-256",
	"SHA-384",
	"SHA-512",
	"SHA3-224",
	"SHA3-256",
	"SHA3-384",
	"SHA3-512",
	"SHAKE128",
	"SHAKE256",
}

var a Alg
for _, alg := range algs {
	a.Parse(alg) // Call as method
	fmt.Println(a)
}
Output:

UnknownAlg
UnknownAlg
UnknownAlg
UnknownSigAlg
ES224
ES256
ES384
ES512
Ed25519
Ed25519ph
Ed448
UnknownEncAlg
UnknownHshAlg
SHA-224
SHA-256
SHA-384
SHA-512
SHA3-224
SHA3-256
SHA3-384
SHA3-512
SHAKE128
SHAKE256

func (Alg) SigAlg added in v0.0.1

func (a Alg) SigAlg() SigAlg

func (Alg) Use added in v0.0.3

func (a Alg) Use() Use

Use returns the Use.

type B64 added in v0.0.1

type B64 []byte

Type B64 is a Coze addition to Go's base64. B64 is useful for marshaling and unmarshalling structs. B64's underlying type is []byte and is represented in JSON as "RFC 4648 base 64 URI canonical with padding truncated" (b64ut).

When converting integers or other types to B64, `nil` is encoded as "" and zero is encoded as "AA".

Example (MarshalJSON)
h := B64([]byte{0, 255})
b, err := h.MarshalJSON()
if err != nil {
	panic(err)
}

fmt.Println(string(b))
Output:

"AP8"
Example (Non_strict_decode)

Demonstrates that Coze Go will error on non-canonical base 64 encoding. See https://github.com/Cyphrme/Coze/issues/18. The last three characters of example `tmb` is `hOk`, but `hOl` also decodes to the same byte value (in Hex, `84E9`) even though they are different UTF-8 values. Tool for decoding [hOk](https://convert.zamicol.com/#?inAlph=base64&in=hOk&outAlph=Hex) and [hOl](https://convert.zamicol.com/#?inAlph=base64&in=hOl&outAlph=Hex).

As an added concern, Go's base64 ignores new line and carriage return. Thankfully, JSON unmarshal does not, making Coze's interpretation of base 64 non-malleable since Coze is JSON.

// Canonical
f := new(B64Struct)
err := json.Unmarshal([]byte(`{"B":"hOk"}`), f)
if err != nil {
	panic(err)
}
fmt.Println(f)

// Non-canonical (hOk and hOl will decode to the same bytes when non-canonical
// is permitted.)
f2 := new(B64Struct)
err = json.Unmarshal([]byte(`{"B":"hOl"}`), f2)
if err != nil { // Correctly errors
	fmt.Println("unmarshalling error: ", err)
}

// Print Unicode to show that Go is interpreting the string below correctly.
b1 := []byte(fmt.Sprintf(`{"B":"hOk"}`))
b2 := []byte(fmt.Sprintf("{\"B\":\"hOk\n\"}")) // Unicode U+000A is line feed.
b3 := []byte(fmt.Sprintf("{\"B\":\"hOk\r\"}")) // Unicode U+000D is line feed.

fmt.Printf("%U\n", b1)
fmt.Printf("%U\n", b2)
fmt.Printf("%U\n", b3)

fb1 := new(B64Struct)
err = json.Unmarshal(b1, fb1) // Will not error
if err != nil {
	fmt.Println(err)
}
fb2 := new(B64Struct)
err = json.Unmarshal(b2, fb2) // Correctly errors.
if err != nil {
	fmt.Println(err)
}
fb3 := new(B64Struct)
err = json.Unmarshal(b3, fb3) // Correctly errors.
if err != nil {
	fmt.Println(err)
}
Output:

&{hOk}
unmarshalling error:  illegal base64 data at input byte 2
[U+007B U+0022 U+0042 U+0022 U+003A U+0022 U+0068 U+004F U+006B U+0022 U+007D]
[U+007B U+0022 U+0042 U+0022 U+003A U+0022 U+0068 U+004F U+006B U+000A U+0022 U+007D]
[U+007B U+0022 U+0042 U+0022 U+003A U+0022 U+0068 U+004F U+006B U+000D U+0022 U+007D]
invalid character '\n' in string literal
invalid character '\r' in string literal
Example (UnmarshalJSON)
f := new(B64Struct)
err := json.Unmarshal([]byte(`{"B":"AP8"}`), f)
if err != nil {
	panic(err)
}

b, err := Marshal(f)
if err != nil {
	panic(err)
}

fmt.Printf("%s,%#v\n", b, B64(b))
Output:

{"B":"AP8"},eyJCIjoiQVA4In0
Example (Zero_nil)

B64 of nil is "" while B64 of 0 is "AA".

var b []byte
b = nil
fmt.Printf("B64 string nil: `%s`\n", B64(b))

b = []byte{0}
fmt.Printf("B64 string zero: `%s`\n", B64(b))
Output:

B64 string nil: ``
B64 string zero: `AA`

func CanonicalHash added in v0.0.1

func CanonicalHash(input []byte, canon any, hash HshAlg) (digest B64, err error)

CanonicalHash accepts []byte and optional canon and returns digest.

If input is already in canonical form, Hash() may also be called instead.

Example

ExampleCanonicalHash. See also Example_genCad

canon := []string{"alg", "iat", "msg", "tmb", "typ"}
cad, err := CanonicalHash([]byte(GoldenPay), canon, SHA256)
if err != nil {
	panic(err)
}
fmt.Println(cad.String())

// Without canon
cad, err = CanonicalHash([]byte(GoldenPay), nil, SHA256)
if err != nil {
	panic(err)
}
fmt.Println(cad.String())
Output:

4bmwgjkxQJIG2jiLiqq6eKptwTs97lYAFUtS25Rc3DU
Ie3xL77AsiCcb4r0pbnZJqMcfSBqg5Lk0npNJyJ9BC4
Example (GenCad)

See also ExampleCanonicalHash.

fmt.Println(CanonicalHash([]byte(GoldenPay), nil, GoldenKey.Alg.Hash()))
Output:

Ie3xL77AsiCcb4r0pbnZJqMcfSBqg5Lk0npNJyJ9BC4 <nil>
Example (InvalidAlg)

Demonstrates expected behavior for invalid HshAlgs.

_, err := CanonicalHash([]byte(GoldenPay), nil, "")
fmt.Println(err)
_, err = CanonicalHash([]byte(GoldenPay), nil, "test")
fmt.Println(err)
Output:

Hash: invalid HshAlg ""
Hash: invalid HshAlg "test"
Example (Permutations)

Example CanonicalHash for all hashing algos.

canon := []string{"alg", "iat", "msg", "tmb", "typ"}
algs := []string{"SHA-224", "SHA-256", "SHA-384", "SHA-512", "SHA3-224", "SHA3-256", "SHA3-384", "SHA3-512", "SHAKE128", "SHAKE256"}
for _, alg := range algs {
	cad, err := CanonicalHash([]byte(GoldenPay), canon, ParseHashAlg(alg))
	if err != nil {
		panic(err)
	}
	fmt.Println(cad.String())
}
Output:

cGCQ6FHj0fjAyYbvxS_8sfC0qTaSLJtcu0Xkhw
4bmwgjkxQJIG2jiLiqq6eKptwTs97lYAFUtS25Rc3DU
WQiyyY5Ye2Y8vKcbANlmiXJkU-SVEgboYJg-wnrOKJ3v8PcI5XvQu_-C4yyGFrbW
irByY6uGnp6DrPvInvggL0ibo2p5yNvcuMVx1GiZoOArVIp4cGkAfB2FvknV5DyzKMHH-tV6vW8TyW7LZOyVFw
9YyKIbtFYbSNqdwAXcwV0lwLb-X65k6zTBTWeQ
8P9aSEJjC8tRKzfLNYBQTTXK-9E-DPlNaH_ikFkYUHQ
suqhBt29HS7c_wwDpcp943h0HlSI_FQdOkiz-Tjf9R_Wegil2pXHVxIFXkpOaceP
dAzMJWHLnGw9kjeo4RbVhzAAL6bwGasQbLFLZ1kHhdhGNNQm5nMib0cAQAAoIwdnKf0L8RADELg1XSFd8aJKww
QzbJ9ONj21KF3Zno1ctdIHfpGqmFGm11tinsAJUkYOg
MFYCTNhmavKZmFk_JNcttN9ccm4MAuYN3T868B2q0olpJ_6po2l98-617RfjnxkVuY2J--JjKt-KGi1S2RL4Bw

func Decode added in v0.0.1

func Decode(b64 string) (B64, error)

Decode decodes a b64ut string.

Example
b, err := Decode(GoldenTmb)
if err != nil {
	panic(err)
}
fmt.Println(b)
Output:

cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk

func GenCzd added in v0.0.1

func GenCzd(hash HshAlg, cad B64, sig B64) (czd B64, err error)

GenCzd generates and returns `czd`.

func Hash added in v0.0.1

func Hash(h HshAlg, msg []byte) (digest B64, err error)

Hash hashes msg and returns the digest. Returns nil on error. Errors on invalid HshAlg or if the resulting digest is empty (as a sanity check).

For algorithms that support arbitrary sized digests, Hash only returns a static size. (SHAKE128 returns 32 bytes and SHAKE256 returns 64 bytes.)

func MustDecode added in v0.0.1

func MustDecode(b64 string) B64

MustDecode decodes b64ut and panics on error.

Example
fmt.Println(MustDecode(GoldenTmb))
Output:

cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk

func PadInts added in v0.0.1

func PadInts(r, s *big.Int, size int) (out B64)

PadInts creates a big-endian byte slice with given size that is the left padded concatenation of two input integers. Parameter `size` must be even. From Go's packages, X, Y, R, and S are type big.Int of varying size. Before encoding to fixed size string, left padding of bytes is needed.

Algorithm notes: EdDSA is little-endian while ECDSA is big-endian. EdDSA should not be used with this function.

For ECDSA, Coze's `x` and `sig` is left padded concatenation of X || Y and R || S respectively.

Note: ES512's signature size is 132 bytes (and not 128, 131, or 130.25), because R and S are each respectively rounded up and padded to 528 and for a total signature size of 1056 bits. See https://datatracker.ietf.org/doc/html/rfc4754#section-7

func Thumbprint

func Thumbprint(c *Key) (tmb B64, err error)

Thumbprint generates `tmb` which is the digest of canon [alg, x]. X must be set and be a valid length. On error, tmb is set to nil.

Example
fmt.Println(Thumbprint(&GoldenKey))
Output:

cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk <nil>

func (B64) GoString added in v0.0.1

func (t B64) GoString() string

GoString implements fmt.GoStringer. Use with `%#v` (not %s or %+v).

func (B64) MarshalJSON added in v0.0.1

func (t B64) MarshalJSON() ([]byte, error)

MarshalJSON implements json.Marshaler. Error is always nil.

func (B64) String added in v0.0.1

func (t B64) String() string

String implements fmt.Stringer. Use with `%s`, `%v`, `%+v`.

func (*B64) UnmarshalJSON added in v0.0.1

func (t *B64) UnmarshalJSON(b []byte) error

UnmarshalJSON implements json.Unmarshaler.

type Coze added in v0.0.1

type Coze struct {
	Pay json.RawMessage `json:"pay,omitempty"`
	Key *Key            `json:"key,omitempty"`
	Can []string        `json:"can,omitempty"`
	Cad B64             `json:"cad,omitempty"`
	Sig B64             `json:"sig,omitempty"`
	Czd B64             `json:"czd,omitempty"`

	Parsed *Pay `json:"-"`
}

Coze is for signed Coze objects (cozies). See the Coze docs (README.md) for more on the construction of `coze`.

Pay: The raw Payload.
Key: Key used to sign the message. Must be pointer, otherwise
  json.Marshal will not marshal on zero type.
  See: https://github.com/golang/go/issues/11939.
Can: "Canon" Pay's fields in order of appearance.
Cad: "Canonical Digest" Pay's compactified form digest.
Sig: Signature over `cad`.
Czd: "Coze digest" with canon ["cad","sig"].
Parsed: The standard Coze pay fields ["alg","iat","tmb","typ"] parsed
  from `Pay`.  `Parsed` is populated by Meta() and is JSON ignored.
Example (Embed)

ExampleCoze_embed demonstrates how to embed a JSON `coze` into a third party JSON structure.

cz := new(Coze)
err := json.Unmarshal([]byte(GoldenCoze), cz)
if err != nil {
	panic(err)
}

type Outer struct {
	Name string `json:"name"`
	Coze Coze   `json:"coze"` // Embed a Coze into a larger, application defined JSON structure.
}
b, _ := json.Marshal(Outer{Name: "Bob", Coze: *cz})
fmt.Printf("%s", b)
Output:

{"name":"Bob","coze":{"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"sig":"Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_w"}}
Example (JsonMarshal)
cz := new(Coze)
err := json.Unmarshal([]byte(GoldenCoze), cz)
if err != nil {
	panic(err)
}

b, err := Marshal(cz)
if err != nil {
	panic(err)
}
fmt.Printf("%+s\n", b)
Output:

{"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"sig":"Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_w"}
Example (JsonMarshalPretty)
cz := new(Coze)
err := json.Unmarshal([]byte(GoldenCoze), cz)
if err != nil {
	panic(err)
}

b, err := MarshalPretty(cz)
if err != nil {
	panic(err)
}
fmt.Printf("%+s\n", b)
Output:

	{
    "pay": {
        "msg": "Coze Rocks",
        "alg": "ES256",
        "iat": 1623132000,
        "tmb": "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk",
        "typ": "cyphr.me/msg"
    },
    "sig": "Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_w"
}
Example (JsonUnmarshal)

ExampleCoze_jsonUnmarshal tests unmarshalling a coze.

cz := new(Coze)
err := json.Unmarshal([]byte(GoldenCoze), cz)
if err != nil {
	panic(err)
}

// remarshal for comparison
b, err := Marshal(cz)
if err != nil {
	panic(err)
}
fmt.Println(string(b))
Output:

{"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"sig":"Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_w"}

func (*Coze) Meta added in v0.0.1

func (cz *Coze) Meta() (err error)

Meta calculates [can, cad, czd] and sets Coze.Parsed ["alg","iat","tmb","typ"] from Pay. Coze.Pay, Coze.Pay.Alg, and Coze.Sig must be set. Meta resets Parsed ("alg","iat","tmb","typ") to zero before populating Parsed from Pay. If needing to use for contextual cozies, use "MetaWithAlg".

Meta does no cryptographic verification.

Example
cz := new(Coze)
err := json.Unmarshal([]byte(GoldenCoze), cz)
if err != nil {
	panic(err)
}

err = cz.Meta()
if err != nil {
	panic(err)
}
fmt.Printf("%s\n", cz)
Output:

{"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"can":["msg","alg","iat","tmb","typ"],"cad":"Ie3xL77AsiCcb4r0pbnZJqMcfSBqg5Lk0npNJyJ9BC4","sig":"Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_w","czd":"TnRe4DRuGJlw280u3pGhMDOIYM7ii7J8_PhNuSScsIU"}

func (*Coze) MetaWithAlg added in v0.0.1

func (cz *Coze) MetaWithAlg(alg SEAlg) (err error)

MetaWithAlg is for contextual cozies that may be lacking `alg` in `pay`, but `alg` in otherwise known. MetaWithAlg recalculates [can, cad, czd] and sets Coze.Parsed ("alg","iat","tmb","typ") from Pay. Does not calculate `czd` if Coze.Sig is empty.

Errors on 1. Invalid JSON. 2. No alg is given. (both coze.pay.alg and alg are empty). 3. Mismatched Pay.Alg and parameter alg if both are set.

MetaWithAlg does no cryptographic verification.

Example
cz := new(Coze)
err := json.Unmarshal([]byte(GoldenCoze), cz)
if err != nil {
	panic(err)
}

// coze.pay.alg given and parameter alg given.
err = cz.MetaWithAlg(SEAlg(ES256))
if err != nil {
	panic(err)
}
fmt.Printf("%s\n", cz)

// coze.pay.alg given and parameter alg not given.  (Alg is parsed from pay).
err = cz.MetaWithAlg("")
if err != nil {
	panic(err)
}
fmt.Printf("%s\n", cz)

// Test mismatch alg, which must error.
err = cz.MetaWithAlg(SEAlg(ES224))
if err == nil {
	fmt.Println("Test must error")
}

// Test no coze.pay.alg or alg given, which must error.
cz2 := new(Coze)
err = json.Unmarshal(GoldenCozeNoAlg, cz2)
if err != nil {
	panic(err)
}
err = cz2.Meta()
if err == nil {
	fmt.Println("Test must error")
}

// Test no coze.pay.alg but alg is given (contextual coze)
err = cz2.MetaWithAlg(SEAlg(ES256))
if err != nil {
	panic(err)
}
fmt.Printf("%s\n", cz2)

// Test no coze.pay.alg or coze.sig, so czd should not be calculated
cz3 := new(Coze)
err = json.Unmarshal(GoldenPayNoAlg, &cz3.Pay)
if err != nil {
	panic(err)
}
err = cz3.MetaWithAlg(SEAlg(ES256))
if err != nil {
	panic(err)
}
fmt.Printf("%s\n", cz3)
Output:

{"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"can":["msg","alg","iat","tmb","typ"],"cad":"Ie3xL77AsiCcb4r0pbnZJqMcfSBqg5Lk0npNJyJ9BC4","sig":"Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_w","czd":"TnRe4DRuGJlw280u3pGhMDOIYM7ii7J8_PhNuSScsIU"}
{"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"can":["msg","alg","iat","tmb","typ"],"cad":"Ie3xL77AsiCcb4r0pbnZJqMcfSBqg5Lk0npNJyJ9BC4","sig":"Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_w","czd":"TnRe4DRuGJlw280u3pGhMDOIYM7ii7J8_PhNuSScsIU"}
{"pay":{"msg":"Coze Rocks","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"can":["msg","iat","tmb","typ"],"cad":"K6MVyIFqhBhLvNafZ8sMCRpCqR1oeFpowi7j8P1uE0M","sig":"reOiKUO--OwgTNlYpKN60_gZARnW5X6PmQw4zWYbz2QryetRg_qS4KvwEVe1aiSAsWlkVA3MqYuaIM5ihY_8NQ","czd":"g6kRqHesiST6L38eZPcTk4Bq-fCxtbD6jTvRS8LKMv8"}
{"pay":{"msg":"Coze Rocks","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"can":["msg","iat","tmb","typ"],"cad":"K6MVyIFqhBhLvNafZ8sMCRpCqR1oeFpowi7j8P1uE0M"}
Example (Contextual)
// Test MetaWithAlg using no sig, which should calc what it can.
cz := new(Coze)
err := json.Unmarshal([]byte(GoldenCoze), cz)
if err != nil {
	panic(err)
}
cz.Sig = []byte{} // set sig to nothing.
err = cz.MetaWithAlg("")
if err != nil {
	panic(err)
}
fmt.Printf("%s\n", cz)

// Empty coze with coze.parsed.alg
cz = new(Coze)
err = json.Unmarshal(GoldenCozeEmpty, cz)
if err != nil {
	panic(err)
}
err = cz.MetaWithAlg(GoldenKey.Alg)
if err != nil {
	panic(err)
}

fmt.Printf("%s\n", cz)
Output:

{"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"can":["msg","alg","iat","tmb","typ"],"cad":"Ie3xL77AsiCcb4r0pbnZJqMcfSBqg5Lk0npNJyJ9BC4"}
{"pay":{},"cad":"RBNvo1WzZ4oRRq0W9-hknpT7T8If536DEMBg9hyq_4o","sig":"9iesKUSV7L1-xz5yd3A94vCkKLmdOAnrcPXTU3_qeKRRbHuy5EvMMFNRkW_sNLo-vvEPO9BmeUkcNh-ok18I_A","czd":"zU7xRwp8XU_VmdOLNBlMBualhoyHiM_cGhib6LPwWlc"}

func (Coze) String added in v0.0.1

func (cz Coze) String() string

String implements fmt.Stringer. Without this method `pay` prints as bytes. On error, returns the error as a string.

Example
cz := new(Coze)
err := json.Unmarshal([]byte(GoldenCoze), cz)
if err != nil {
	panic(err)
}
fmt.Println(cz)
Output:

{"pay":{"msg":"Coze Rocks","alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"},"sig":"Jl8Kt4nznAf0LGgO5yn_9HkGdY3ulvjg-NyRGzlmJzhncbTkFFn9jrwIwGoRAQYhjc88wmwFNH5u_rO56USo_w"}

func (*Coze) UnmarshalJSON added in v0.0.1

func (cz *Coze) UnmarshalJSON(b []byte) error

UnmarshalJSON unmarshals checks for duplicates and unmarshals `coze`. See notes on Pay.UnmarshalJSON.

Example (Duplicate)

Example demonstrating that unmarshalling a `coze` that has duplicate field names results in an error.

h := &Pay{}
msg := []byte(`{"coze":{"pay":"ES256","pay":"ES384"}}`)
err := json.Unmarshal(msg, h)
fmt.Println(err)
Output:

Coze: JSON duplicate field "pay"

type Crv added in v0.0.1

type Crv string // Curve type.  Used for EC curves.
const (
	UnknownCrv Crv = "UnknownCrv"
	P224       Crv = "P-224"
	P256       Crv = "P-256"
	P384       Crv = "P-384"
	P521       Crv = "P-521"
	Curve25519 Crv = "Curve25519"
	Curve448   Crv = "Curve448"
)

func (Crv) EllipticCurve added in v0.0.1

func (c Crv) EllipticCurve() elliptic.Curve

Curve returns Go's elliptic.Curve for the given crv. Returns nil if there is no matching `elliptic.Curve`.

func (*Crv) Parse added in v0.0.1

func (c *Crv) Parse(s string)
Example
var c Crv

crvs := []string{
	"P-224",
	"P-256",
	"P-384",
	"P-521",
	"Curve25519",
	"Curve448",
}

for _, crv := range crvs {
	c.Parse(crv)
	fmt.Println(c)
}
Output:

P-224
P-256
P-384
P-521
Curve25519
Curve448

type EncAlg added in v0.0.1

type EncAlg SEAlg // Encryption Algorithm

type ErrJSONDuplicate added in v0.0.3

type ErrJSONDuplicate error

ErrJSONDuplicate allows applications to check for JSON duplicate error.

type FamAlg added in v0.0.1

type FamAlg string // Algorithm family
const (
	UnknownFamAlg FamAlg = "UnknownFamAlg"
	EC            FamAlg = "EC"
	SHA           FamAlg = "SHA"
	RSA           FamAlg = "RSA"
)

FamAlg "Family".

type GenAlg added in v0.0.1

type GenAlg string // Algorithm genus.
const (
	UnknownGenAlg GenAlg = "UnknownGenAlg"
	ECDSA         GenAlg = "ECDSA"
	EdDSA         GenAlg = "EdDSA"
	SHA2          GenAlg = "SHA2"
	SHA3          GenAlg = "SHA3"
)

GenAlg "Genus".

type HshAlg added in v0.0.3

type HshAlg Alg // Hashing Algorithm
Example (JsonMarshal)
type testStruct = struct {
	H HshAlg `json:"hshAlg"`
}
z := testStruct{H: SHA256}
jm, err := Marshal(z)
if err != nil {
	panic(err)
}
fmt.Printf("%+s\n", jm)
Output:

{"hshAlg":"SHA-256"}
Example (Print)
h := SHA256
fmt.Println(h)
Output:

SHA-256

func ParseHashAlg added in v0.0.1

func ParseHashAlg(s string) HshAlg

func (*HshAlg) Parse added in v0.0.3

func (h *HshAlg) Parse(s string)

func (HshAlg) Size added in v0.0.3

func (h HshAlg) Size() int

HashSize returns the digest size in bytes for the given hashing algorithm.

For SHAKE128 and SHAKE256, this function returns the static sizes, 32 and 64 respectively, although the algorithm permits any larger arbitrary output size. SHAKE128 has 128 bits of pre-collision resistance and a capacity of 256, although it has arbitrary output size. SHAKE256 has 256 bits of pre-collision resistance and a capacity of 512, although it has arbitrary output size.

type Key added in v0.0.1

type Key struct {
	Alg SEAlg  `json:"alg,omitempty"`
	D   B64    `json:"d,omitempty"`
	Iat int64  `json:"iat,omitempty"`
	Kid string `json:"kid,omitempty"`
	Rvk int64  `json:"rvk,omitempty"`
	Tmb B64    `json:"tmb,omitempty"`
	Typ string `json:"typ,omitempty"`
	X   B64    `json:"x,omitempty"`
}

Key is a Coze key. See `README.md` for details on Coze key. Fields `alg` and `tmb` must be in correct relative order for thumbprint canon because JSON marshal uses struct order.

Standard Coze key Fields

`alg` - Specific key algorithm. E.g. "ES256" or "Ed25519".
`d`   - Private component. E.g. "bNstg4_H3m3SlROufwRSEgibLrBuRq9114OvdapcpVA".
`iat` - Unix time of when the key was created. E.g. 1626069600.
`kid` - Human readable, non-programmatic label. E.g. "My Coze key".
`rvk` - Unix time of key revocation. See docs on `rvk`. E.g. 1626069601.
`tmb` - Key thumbprint. E.g. "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk".
`typ` - Application label for key. E.g. "coze/key".
`x`   - Public component. E.g. "2nTOaFVm2QLxmUO_SjgyscVHBtvHEfo2rq65MvgNRjORojq39Haq9rXNxvXxwba_Xj0F5vZibJR3isBdOWbo5g".
Example (JsonMarshal)
b, err := Marshal(GoldenKey)
if err != nil {
	panic(err)
}
fmt.Printf("%s\n", string(b))
Output:

{"alg":"ES256","d":"bNstg4_H3m3SlROufwRSEgibLrBuRq9114OvdapcpVA","iat":1623132000,"kid":"Zami's Majuscule Key.","tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","x":"2nTOaFVm2QLxmUO_SjgyscVHBtvHEfo2rq65MvgNRjORojq39Haq9rXNxvXxwba_Xj0F5vZibJR3isBdOWbo5g"}
Example (JsonUnmarshal)

ExampleKey_jsonUnmarshal tests unmarshalling a Coze key.

Key := new(Key)
err := json.Unmarshal([]byte(GoldenKeyString), Key)
if err != nil {
	panic(err)
}
fmt.Printf("%+v\n", Key)
Output:

{"alg":"ES256","d":"bNstg4_H3m3SlROufwRSEgibLrBuRq9114OvdapcpVA","iat":1623132000,"kid":"Zami's Majuscule Key.","tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","x":"2nTOaFVm2QLxmUO_SjgyscVHBtvHEfo2rq65MvgNRjORojq39Haq9rXNxvXxwba_Xj0F5vZibJR3isBdOWbo5g"}
Example (Unmarshal)

Example demonstrating that unmarshal generates `tmb` from x,

var GoldenPukNoTmb = json.RawMessage(`{
		"alg":"ES256",
		"iat":1623132000,
		"kid":"Zami's Majuscule Key.",
		"x":"2nTOaFVm2QLxmUO_SjgyscVHBtvHEfo2rq65MvgNRjORojq39Haq9rXNxvXxwba_Xj0F5vZibJR3isBdOWbo5g"
	}`)
czk := new(Key)
err := json.Unmarshal(GoldenPukNoTmb, czk)
if err != nil {
	panic(err)
}
fmt.Println(czk)
Output:

 {"alg":"ES256","iat":1623132000,"kid":"Zami's Majuscule Key.","tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","x":"2nTOaFVm2QLxmUO_SjgyscVHBtvHEfo2rq65MvgNRjORojq39Haq9rXNxvXxwba_Xj0F5vZibJR3isBdOWbo5g"}

func NewKey

func NewKey(alg SEAlg) (c *Key, err error)

NewKey generates a new Coze key.

Example
algs := []SigAlg{
	ES224,
	ES256,
	ES384,
	ES512,
	Ed25519,
}

for _, alg := range algs {
	Key, err := NewKey(SEAlg(alg))
	if err != nil {
		panic(err)
	}
	fmt.Printf("%s, %t\n", Key.Alg, Key.Valid())
}
Output:

ES224, true
ES256, true
ES384, true
ES512, true
Ed25519, true
Example (Bad)
fmt.Println(NewKey(SEAlg(SHA256))) // Invalid signing alg, fails.
Output:

<nil> NewKey: unsupported alg "SHA-256"
Example (Valid)
ck, err := NewKey(SEAlg(ES256))
if err != nil {
	panic(err)
}
fmt.Println(ck.Valid())
Output:

true

func (*Key) Correct added in v0.0.1

func (c *Key) Correct() (err error)

Correct is an advanced function for checking for the correct construction of a Coze key if it can be known from the given inputs. Key must have at least one of [`tmb`, `x`,`d`] and `alg` set. Correct may return no error on cryptographically invalid public keys. Using input information, if possible to definitively know the given key is incorrect, Correct returns an error, but if plausibly correct, Correct returns no error. Correct answers the question: "Is the given Coze key reasonable using the information provided?". Correct is useful for sanity checking public keys without signed messages, sanity checking `tmb` only keys, and validating private keys. Use function "Verify" instead for verifying public keys when a signed message is available. Correct is considered an advanced function. Please understand it thoroughly before use.

Correct:

  1. Checks the length of `x` and/or `tmb` against `alg`.
  2. If `x` and `tmb` are present, verifies correct `tmb`.
  3. If `d` is present, verifies correct `tmb` and `x` if present, and verifies the key by verifying a generated signature.
  4. If possible, sets tmb and/or x.

Functions that call correct can check for correctness by `if key.Correct() != nil`

Example

ExampleKey_Correct demonstrates the expectations from Correct() when different key fields appear. Note that some calls to Correct() pass on _invalid_ keys depending on given fields.

// helper print function
tf := func(err ...error) {
	for i, e := range err {
		if e != nil {
			fmt.Printf("%t", false)
		} else {
			fmt.Printf("%t", true)
		}
		if i < len(err)-1 {
			fmt.Printf(", ")
		}
	}
}

keys := []Key{GoldenKeyBadD, GoldenKeyBadX, GoldenKey}
// Test new keys.  These keys should pass every test.
algs := []string{"ES224", "ES256", "ES384", "ES512", "Ed25519"}
for _, alg := range algs {
	key, err := NewKey(SEAlg(Parse(alg)))
	if err != nil {
		panic(err)
	}
	keys = append(keys, *key)
}

for _, k := range keys {
	gk2 := k // Make a copy

	// Key with with [alg,d,tmb,x]
	p1 := gk2.Correct()

	// A key with [alg,tmb,d]
	gk2 = k
	gk2.X = []byte{}
	p2 := gk2.Correct()

	// Key with [alg,d].
	gk2 = k
	gk2.X = []byte{}
	gk2.Tmb = []byte{}
	p3 := gk2.Correct()

	// A key with [alg,x,d].
	gk2 = k
	gk2.Tmb = []byte{}
	p4 := gk2.Correct()

	// A key with [alg,x,tmb]
	gk2 = k
	gk2.D = []byte{}
	p5 := gk2.Correct()

	// Key with [alg,tmb]
	gk2 = k
	gk2.D = []byte{}
	gk2.X = []byte{}
	p6 := gk2.Correct()

	tf(p1, p2, p3, p4, p5, p6)
	fmt.Printf("\n")
}
Output:

false, false, true, false, true, true
false, false, true, false, false, true
true, true, true, true, true, true
true, true, true, true, true, true
true, true, true, true, true, true
true, true, true, true, true, true
true, true, true, true, true, true
true, true, true, true, true, true

func (Key) IsRevoked added in v0.0.1

func (c Key) IsRevoked() bool

IsRevoked returns true if the given Key is marked as revoked.

Example
gk2 := GoldenKey // Make a copy
fmt.Println(gk2.IsRevoked())
coze, err := gk2.Revoke()
if err != nil {
	panic(err)
}

pay := new(Pay)
err = pay.UnmarshalJSON(coze.Pay)
if err != nil {
	panic(err)
}
// Both the revoke coze and the key should be interpreted as revoked.
fmt.Println(pay.IsRevoke())
fmt.Println(gk2.IsRevoked())
Output:

false
true
true

func (*Key) Revoke added in v0.0.1

func (c *Key) Revoke() (coze *Coze, err error)

Revoke returns a signed revoke coze and sets `rvk` on the key itself.

func (*Key) Sign added in v0.0.1

func (c *Key) Sign(digest B64) (sig B64, err error)

Sign uses a private Coze key to sign a digest.

Sign() and Verify() do not check if the Coze is correct, such as checking pay.alg and pay.tmb matches with Key. Use SignPay, SignCoze, SignPayJSON, and/or VerifyCoze if needing Coze validation.

Example
// Manual signing of empty Coze, `{"pay":{},"sig":"9iesKU..."}`, is a valid
// Coze.  In this case, it would be better to use SignPayJSON.
d, err := Hash(GoldenKey.Alg.Hash(), []byte("{}"))
if err != nil {
	panic(err)
}
sig, err := GoldenKey.Sign(d)
if err != nil {
	panic(err)
}
fmt.Println(GoldenKey.Verify(d, sig))

// Signing a previously known cad.
cad := MustDecode(GoldenCad)
sig, err = GoldenKey.Sign(cad)
if err != nil {
	panic(err)
}
fmt.Printf("%v\n", GoldenKey.Verify(cad, sig))
Output:

true
true

func (*Key) SignCoze added in v0.0.1

func (c *Key) SignCoze(cz *Coze) (err error)

SignCoze signs `coze.pay` and sets `coze.sig`. See documentation on SignPay.

Example
cz := new(Coze)
cz.Pay = json.RawMessage(GoldenPay)
err := GoldenKey.SignCoze(cz)
if err != nil {
	panic(err)
}

fmt.Println(GoldenKey.VerifyCoze(cz))
Output:

true <nil>

func (*Key) SignPay added in v0.0.1

func (c *Key) SignPay(p *Pay) (coze *Coze, err error)

SignPay signs coze.Pay and returns a new Coze with coze.Sig populated. If set, SignPay checks that `pay.alg` and `key.alg` match and that `pay.tmb` is correct according to `key`.

SignPay works with contextual cozies that lack pay.alg and/or pay.tmb and uses key as a source of truth.

Example

ExampleKey_SignPay demonstrates converting a custom data structure into a coze, signing it, and verifying the results.

customStruct := CustomStruct{
	Msg: "Coze Rocks",
}

pay := Pay{
	Alg:    SEAlg(ES256),
	Iat:    1623132000, // Static for demonstration.  Use time.Now().Unix().
	Tmb:    MustDecode("cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk"),
	Typ:    "cyphr.me/msg",
	Struct: customStruct,
}

coze, err := GoldenKey.SignPay(&pay)
if err != nil {
	panic(err)
}

fmt.Println(GoldenKey.VerifyCoze(coze))
Output:

true <nil>

func (*Key) SignPayJSON added in v0.0.1

func (c *Key) SignPayJSON(pay json.RawMessage) (coze *Coze, err error)

SignPayJSON signs a json `coze.pay`. See documentation on SignPay.

Example
coze, err := GoldenKey.SignPayJSON(json.RawMessage(GoldenPay))
if err != nil {
	panic(err)
}
fmt.Println(GoldenKey.VerifyCoze(coze))
Output:

true <nil>
Example (Empty)

ExampleKey_Sign_empty demonstrates signing of empty Coze, `{"pay":{},"sig":"9iesKU..."}`, is valid.

coze, err := GoldenKey.SignPayJSON([]byte("{}"))
if err != nil {
	panic(err)
}
fmt.Println(GoldenKey.VerifyCoze(coze))
Output:

true <nil>

func (Key) String added in v0.0.1

func (c Key) String() string

String implements Stringer. Returns empty on error.

Example
fmt.Printf("%s\n", GoldenKey)
Output:

{"alg":"ES256","d":"bNstg4_H3m3SlROufwRSEgibLrBuRq9114OvdapcpVA","iat":1623132000,"kid":"Zami's Majuscule Key.","tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","x":"2nTOaFVm2QLxmUO_SjgyscVHBtvHEfo2rq65MvgNRjORojq39Haq9rXNxvXxwba_Xj0F5vZibJR3isBdOWbo5g"}

func (*Key) Thumbprint added in v0.0.1

func (c *Key) Thumbprint() (err error)

Thumbprint generates `tmb` which is the digest of canon [alg, x]. X must be set and be a valid length. On error, tmb is set to nil.

Example
gk2 := GoldenKey   // Make a copy.
gk2.Tmb = []byte{} // Set to empty to ensure recalculation.
err := gk2.Thumbprint()
fmt.Println(gk2.Tmb, err)
Output:

cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk <nil>

func (*Key) ToPubEcdsa added in v0.0.4

func (c *Key) ToPubEcdsa() (key *ecdsa.PublicKey)

KeyToPubEcdsa converts a Coze Key to ecdsa.PublicKey.

func (*Key) UnmarshalJSON added in v0.0.4

func (c *Key) UnmarshalJSON(b []byte) error

UnmarshalJSON always populates `tmb` even if it isn't given.

Example (Duplicate)

Example demonstrating that unmarshalling a `pay` that has duplicate field names results in an error.

k := &Key{}
msg := []byte(`{"alg":"ES256","alg":"ES256"}`)
err := json.Unmarshal(msg, k)
fmt.Println(err)
Output:

Coze: JSON duplicate field "alg"

func (*Key) Valid added in v0.0.1

func (c *Key) Valid() (valid bool)

Valid cryptographically validates a private Coze Key by signing a message and verifying the resulting signature with the given "x".

Valid always returns false on public keys. Use function "Verify" for public keys with signed message. See also function Correct.

Example

Tests valid on a good Coze key and a bad Coze key

fmt.Println(GoldenKey.Valid(), GoldenKeyBadD.Valid())
Output:

true false

func (*Key) Verify added in v0.0.1

func (c *Key) Verify(digest, sig B64) (valid bool)

Verify uses a Coze key to verify a digest. Typically digest is `cad`.

Sign() and Verify() do not check if the coze is correct, such as checking pay.alg and pay.tmb matches with Key. Use SignPay, SignCoze, SignPayJSON, and/or VerifyCoze if needing Coze validation.

Example
fmt.Println(GoldenKey.Verify(MustDecode(GoldenCad), MustDecode(GoldenSig)))
Output:

true
Example (Empty)

Example demonstrating the verification of the empty coze from the README.

cz := new(Coze)
err := json.Unmarshal(GoldenCozeEmpty, cz)
if err != nil {
	panic(err)
}
fmt.Println(GoldenKey.VerifyCoze(cz))
Output:

true <nil>

func (*Key) VerifyCoze added in v0.0.1

func (c *Key) VerifyCoze(cz *Coze) (bool, error)

VerifyCoze cryptographically verifies `pay` with given `sig`. If set VerifyCoze checks that `pay.alg` and `key.alg` match and that `pay.tmb` is correct according to `key`. Always returns false on error.

VerifyCoze works with contextual cozies that lack pay.alg and/or pay.tmb and uses key as a source of truth.

Example
cz := new(Coze)
err := json.Unmarshal([]byte(GoldenCoze), cz)
if err != nil {
	panic(err)
}

fmt.Println(GoldenKey.VerifyCoze(cz))
Output:

true <nil>

type Marshaler added in v0.0.1

type Marshaler interface {
	CozeMarshal() ([]byte, error)
}

Marshaler is a UTF-8 marshaller for Go structs. Go's `json.Marshal` removes the valid characters "&". "<", ">". See note on Marshal. (Note that Marshaler follows the Go "-er" convention for interfaces while marshaller is spelled with two l's)

type Params added in v0.0.1

type Params struct {
	Name        string
	Genus       GenAlg `json:"Genus"`
	Family      FamAlg `json:"Family"`
	Use         Use    `json:"Use,omitempty"`
	Hash        HshAlg `json:"Hash,omitempty"` // Hash
	HashSize    int    `json:"HashSize,omitempty"`
	HashSizeB64 int    `json:"HashSizeB64,omitempty"`
	XSize       int    `json:"XSize,omitempty"` // Key
	XSizeB64    int    `json:"XSizeB64,omitempty"`
	DSize       int    `json:"DSize,omitempty"`
	DSizeB64    int    `json:"DSizeB64,omitempty"`
	Curve       Crv    `json:"Curve,omitempty"`
	SigSize     int    `json:"SigSize,omitempty"` // Sig
	SigSizeB64  int    `json:"SigSizeB64,omitempty"`
}

Params reports all relevant parameters for an `alg`. If a parameter is not applicable for a particular `alg`, its value is be populated with the Go zero value, e.g. for the hash alg "SHA-256" Curve's value is 0 and omitted from JSON marshaling.

type Pay added in v0.0.1

type Pay struct {
	Alg SEAlg  `json:"alg,omitempty"` // e.g. "ES256"
	Iat int64  `json:"iat,omitempty"` // e.g. 1623132000
	Tmb B64    `json:"tmb,omitempty"` // e.g. "cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk"
	Typ string `json:"typ,omitempty"` // e.g. "cyphr.me/msg/create"

	// Rvk is only for revoke messages.
	Rvk int64 `json:"rvk,omitempty"` // e.g. 1623132000

	// Custom arbitrary struct given by application.
	Struct any `json:"-"`
}

Pay contains the standard Coze pay fields as well as custom struct given by third party applications. This allows embedding third party structs into Pay for creating custom cozies (see example ExampleKey_SignPay).

The JSON tags on [Alg, Iat, Tmb, Typ, Rvk, Struct] are ineffective due to the custom MarshalJSON(), however they are present for documentation.

`Struct` will be marshaled when not empty. The custom marshaller promotes fields inside `Struct` to be top level fields inside of `pay`. The tag `json:"-"` is ignored by the custom marshaller, and is set to "-" so that the default marshaller does not include it.

iat and rvk are type int64 and not uint64 to follow the advised type for third party time fields.

Example (Embedded)
// Example custom struct.
type User struct {
	DisplayName string
	FirstName   string
	LastName    string
	Email       string `json:",omitempty"` // Example of non-required field.
}

user := User{
	DisplayName: "Coze",
	FirstName:   "Foo",
	LastName:    "Bar",
}

// Example of converting a custom struct to a coze.
pay := Pay{
	Alg:    GoldenKey.Alg,
	Tmb:    GoldenKey.Tmb,
	Struct: &user,
}

coze, err := GoldenKey.SignPay(&pay)
if err != nil {
	panic(err)
}

v, err := GoldenKey.VerifyCoze(coze)
if err != nil {
	panic(err)
}

// Set sig to nil for deterministic printout
coze.Sig = nil
fmt.Println(v)
fmt.Printf("%+v\n", coze)
Output:

true
{"pay":{"alg":"ES256","tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","DisplayName":"Coze","FirstName":"Foo","LastName":"Bar"}}
Example (JsonMarshalCustom)

ExamplePay_jsonMarshalCustom demonstrates marshalling Pay with a custom structure.

customStruct := CustomStruct{
	Msg: "Coze Rocks",
}

inputPay := Pay{
	Alg:    SEAlg(ES256),
	Iat:    1623132000, // Static for demonstration.  Use time.Now().Unix().
	Tmb:    MustDecode("cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk"),
	Typ:    "cyphr.me/msg",
	Struct: customStruct,
}

// May also call inputPay.MarshalJSON() or Marshal(&inputPay) instead.
s, err := Marshal(&inputPay)
if err != nil {
	panic(err)
}
fmt.Println(string(s))
Output:

{"alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg","msg":"Coze Rocks"}
Example (JsonUnmarshal)

ExamplePay_jsonUnmarshal tests unmarshalling a Pay.

h := &Pay{}

err := json.Unmarshal([]byte(GoldenPay), h)
if err != nil {
	panic(err)
}

out, err := Marshal(h)
if err != nil {
	panic(err)
}
fmt.Printf("%s\n", out)
Output:

{"alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"}
Example (JsonUnmarshalCustom)

ExamplePay_jsonUnmarshalCustom demonstrates unmarshalling Pay with a custom structure.

pay := new(Pay)
var emptyCustomStruct CustomStruct
pay.Struct = &emptyCustomStruct
err := json.Unmarshal([]byte(GoldenPay), &pay)
if err != nil {
	fmt.Printf("Unmarshal error: %s\n", err)
}
fmt.Println(pay)
fmt.Println(pay.Struct)
Output:

{"alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg","msg":"Coze Rocks"}
&{Coze Rocks}
Example (JsonUnmarshalCustomManual)

ExamplePay_jsonUnmarshalCustomManual demonstrates "manually" unmarshalling Pay with a custom structure.

var pay Pay
err := json.Unmarshal([]byte(GoldenPay), &pay)
if err != nil {
	panic(err)
}
fmt.Println(pay)

var custom CustomStruct
err = json.Unmarshal([]byte(GoldenPay), &custom)
if err != nil {
	panic(err)
}
fmt.Println(custom)
Output:

{"alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg"}
{Coze Rocks}

func (*Pay) Coze added in v0.0.1

func (p *Pay) Coze() (coze *Coze, err error)

Pay.Coze() returns a new Coze with only Pay populated.

func (*Pay) IsRevoke added in v0.0.4

func (p *Pay) IsRevoke() bool

IsRevoke returns true if the given Key is marked as revoked.

func (*Pay) MarshalJSON added in v0.0.1

func (p *Pay) MarshalJSON() ([]byte, error)

MarshalJSON promotes the embedded field "Struct" to top level JSON. Solution from Jonathan Hall: https://jhall.io/posts/go-json-tricks-embedded-marshaler

func (Pay) String added in v0.0.1

func (p Pay) String() string

String implements fmt.Stringer. On error, returns the error as a string.

Example (Custom)

ExamplePay_String_custom demonstrates fmt.Stringer on Pay with a custom structure.

customStruct := CustomStruct{
	Msg: "Coze Rocks",
}

inputPay := Pay{
	Alg:    SEAlg(ES256),
	Iat:    1623132000, // Static for demonstration.  Use time.Now().Unix().
	Tmb:    MustDecode("cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk"),
	Typ:    "cyphr.me/msg",
	Struct: customStruct,
}
fmt.Println(inputPay)
Output:

{"alg":"ES256","iat":1623132000,"tmb":"cLj8vsYtMBwYkzoFVZHBZo6SNL8wSdCIjCKAwXNuhOk","typ":"cyphr.me/msg","msg":"Coze Rocks"}

func (*Pay) UnmarshalJSON added in v0.0.1

func (p *Pay) UnmarshalJSON(b []byte) error

UnmarshalJSON unmarshals both Pay and if given custom Pay.Struct. Throws an error on duplicate. (Duplicate related, see https://github.com/golang/go/issues/48298)

Example (Duplicate)

Example demonstrating that unmarshalling a `pay` that has duplicate field names results in an error.

h := &Pay{}
msg := []byte(`{"alg":"ES256","alg":"ES384"}`)
err := json.Unmarshal(msg, h)
fmt.Println(err)
Output:

Coze: JSON duplicate field "alg"

type SB64 added in v0.0.6

type SB64 string

SB64 is useful for B64 map keys. Idiomatically, map key type should be `B64`, but currently in Go map keys are only type `string`, not `[]byte`. Since B64's underlying type is `[]byte` it cannot be used as a map key. See https://github.com/golang/go/issues/283 and https://github.com/google/go-cmp/issues/67. SB64 will be deprecated if/when Go supports []byte keys.

This is an acceptable hack because (from https://go.dev/blog/strings)

>[A] string holds arbitrary bytes. It is not required to hold Unicode text,
> UTF-8 text, or any other predefined format. As far as the content of a
> string is concerned, it is exactly equivalent to a slice of bytes.
Example

ExampleSB64 demonstrates using SB64 as a map key and that fmt prints "RFC 4648 base 64 URI canonical with padding truncated" properly.

b := MustDecode("zVzgRU3WFpnrlVJAnI4ZU1Od4Agl5Zd4jIP79oubOW0")
b2 := MustDecode("vZIAk8rjcSIKZKokGylCtVoI3DXvFYJn4XNWzf_C_FA")

lp := make(map[SB64]B64)
lp[SB64(b)] = B64(b2)

fmt.Printf("%s\n", SB64(b))
fmt.Printf("%s\n", lp)
fmt.Printf("%+v\n", lp)
fmt.Printf("%#v\n", lp)
Output:

zVzgRU3WFpnrlVJAnI4ZU1Od4Agl5Zd4jIP79oubOW0
map[zVzgRU3WFpnrlVJAnI4ZU1Od4Agl5Zd4jIP79oubOW0:vZIAk8rjcSIKZKokGylCtVoI3DXvFYJn4XNWzf_C_FA]
map[zVzgRU3WFpnrlVJAnI4ZU1Od4Agl5Zd4jIP79oubOW0:vZIAk8rjcSIKZKokGylCtVoI3DXvFYJn4XNWzf_C_FA]
map[coze.SB64]coze.B64{zVzgRU3WFpnrlVJAnI4ZU1Od4Agl5Zd4jIP79oubOW0:vZIAk8rjcSIKZKokGylCtVoI3DXvFYJn4XNWzf_C_FA}

func (SB64) GoString added in v0.0.6

func (b SB64) GoString() string

GoString implements fmt.GoString

func (SB64) String added in v0.0.6

func (b SB64) String() string

String implements fmt.Stringer

type SEAlg added in v0.0.1

type SEAlg Alg

SEAlg is the Signing or Encryption alg. Super type of SigAlg and EncAlg and is itself not a specific algorithm and is not included in `Alg`. It is useful for algorithms that need `x` and/or `d` and related functions.

const (
	SEAlgUnknown SEAlg = "UnknownSEAlg"
)

func (SEAlg) Curve added in v0.0.1

func (se SEAlg) Curve() Crv

func (SEAlg) DSize added in v0.0.1

func (se SEAlg) DSize() int

DSize returns the byte size of `d`. Returns 0 on invalid algorithm.

func (SEAlg) Family added in v0.0.1

func (se SEAlg) Family() FamAlg

func (SEAlg) Genus added in v0.0.1

func (se SEAlg) Genus() GenAlg

func (SEAlg) Hash added in v0.0.1

func (se SEAlg) Hash() HshAlg

Hash returns respective hashing algorithm if specified.

func (*SEAlg) Parse added in v0.0.1

func (se *SEAlg) Parse(s string)

func (SEAlg) SigAlg added in v0.0.1

func (se SEAlg) SigAlg() SigAlg

func (SEAlg) SigSize added in v0.0.4

func (se SEAlg) SigSize() int

func (SEAlg) XSize added in v0.0.1

func (se SEAlg) XSize() int

XSize returns the byte size of `x`. Returns 0 on invalid algorithm.

For ECDSA `x` is the concatenation of X and Y.

type SigAlg added in v0.0.1

type SigAlg SEAlg // Signing Algorithm

func (SigAlg) FamAlg added in v0.0.1

func (s SigAlg) FamAlg() FamAlg

func (SigAlg) Genus added in v0.0.1

func (s SigAlg) Genus() GenAlg

func (SigAlg) Hash added in v0.0.1

func (s SigAlg) Hash() HshAlg

Hash returns respective hashing algorithm if specified.

func (SigAlg) SigSize added in v0.0.1

func (s SigAlg) SigSize() int

SigSize returns the signature size for the given Crypto Algorithm.

Ed25519's SigSize is from RFC8032_5.1.6.6.

type Use added in v0.0.3

type Use string // The only valid values are 'sig', 'enc', and 'hsh'.
const (
	UseUnknown Use = "UnknownUse"
	SigUse     Use = "sig" // "Signing Use"
	EncUse     Use = "enc" // "Encryption Use"
	HshUse     Use = "hsh" // "Hash Use"
)

func ParseUse added in v0.0.3

func ParseUse(s string) Use

func (*Use) Parse added in v0.0.3

func (u *Use) Parse(s string)
Example
var u Use
uses := []string{
	"sig",
	"enc",
	"hsh",
}
for _, use := range uses {
	u.Parse(use)
	fmt.Println(u)
}
Output:

sig
enc
hsh

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL