macula_identity (macula v11.4.0)
View SourceEd25519 identities and the S/Kademlia crypto puzzle.
A Macula NodeId is an Ed25519 public key (32 bytes). Identities are optionally "puzzle-hardened": the pubkey satisfies SHA-256(pubkey) having at least N leading zero bits. This raises the cost of mass identity minting (Sybil defence).
The difficulty puzzle_valid/1 applies, and generate/1 grinds to when no difficulty is given, is the macula application's puzzle_difficulty env: 8 leading zero bits when it is unset. A set value must be an integer from 0 to 16: the decided range tops at 12 bits (8 in 10.x, 12 in 11.0.0, plan decision D30), and the maximum allows 4 bits above it. The macula application checks the value when it starts, through check_puzzle_difficulty/0, so a node with a bad value does not start; every use checks it again, for a value set while the node runs.
See plans/PLAN_MACULA_V2_PART1_FOUNDATIONS.md sections 4.1–4.4.
Summary
Functions
Checks the macula application's puzzle_difficulty env: ok when it is unset or an integer from 0 to 16, and raises {bad_config, {macula, puzzle_difficulty, Value}} otherwise. The macula application calls it when it starts.
Generate a fresh Ed25519 key pair whose node id passes the puzzle check at the configured difficulty, as a station applies it. Grinding for it takes milliseconds.
Generate a key pair. It is ground until it satisfies the puzzle, unless puzzle is false, which gives a plain key for a caller that needs one, such as a test of the puzzle check itself.
Load a key pair from disk.
NodeId of an identity. Phase 1: NodeId == public key.
SHA-256 of the public key — the proof-of-work output measured.
Puzzle validity against the macula application's puzzle_difficulty env, 8 leading zero bits when it is unset.
Save a key pair to disk with macula_owner_only_file:write/2: only its owner can read the file, it replaces whatever is at Path atomically, and a missing key directory is created with mode 0700.
Types
Functions
-spec check_puzzle_difficulty() -> ok.
Checks the macula application's puzzle_difficulty env: ok when it is unset or an integer from 0 to 16, and raises {bad_config, {macula, puzzle_difficulty, Value}} otherwise. The macula application calls it when it starts.
-spec generate() -> key_pair().
Generate a fresh Ed25519 key pair whose node id passes the puzzle check at the configured difficulty, as a station applies it. Grinding for it takes milliseconds.
-spec generate(#{puzzle => boolean(), difficulty => non_neg_integer(), _ => _}) -> key_pair().
Generate a key pair. It is ground until it satisfies the puzzle, unless puzzle is false, which gives a plain key for a caller that needs one, such as a test of the puzzle check itself.
Opts:
puzzle:: boolean(), default truedifficulty:: non_neg_integer(), the leading zero bits required
-spec load(file:name_all()) -> {ok, key_pair()} | {error, bad_key_file | macula_owner_only_file:refusal() | term()}.
Load a key pair from disk.
The key file is read with macula_owner_only_file:read/1. It must be a regular file its group and others have no access to, mode 0600 or 0400, owned by the user the node runs as; symlinks are followed. Otherwise the error names the file, what was found and what is required: {file_permissions, #{file, mode, required}}, {file_owner, #{file, owner, required}} or {file_type, #{file, type, required}}. A key file of another owner is never reported as missing, so a caller that makes a new identity only for a missing key file makes none.
NodeId of an identity. Phase 1: NodeId == public key.
SHA-256 of the public key — the proof-of-work output measured.
Puzzle validity against the macula application's puzzle_difficulty env, 8 leading zero bits when it is unset.
-spec puzzle_valid(pubkey() | key_pair(), non_neg_integer()) -> boolean().
-spec save(file:name_all(), key_pair()) -> ok | {error, term()}.
Save a key pair to disk with macula_owner_only_file:write/2: only its owner can read the file, it replaces whatever is at Path atomically, and a missing key directory is created with mode 0700.