From 5c7850b5e33fe6c6437cf6f9b730919fbf351156 Mon Sep 17 00:00:00 2001 From: Dave Grantham Date: Tue, 24 Feb 2026 21:44:40 -0700 Subject: [PATCH 1/2] update to match bettersign impl Signed-off-by: Dave Grantham --- specifications/provenance-logs.md | 24 ++ specifications/vlad.md | 141 ++++---- specifications/wacc-api.md | 568 ++++++++++++++++++++++++++++++ 3 files changed, 653 insertions(+), 80 deletions(-) create mode 100644 specifications/wacc-api.md diff --git a/specifications/provenance-logs.md b/specifications/provenance-logs.md index 195782a..29fcdea 100644 --- a/specifications/provenance-logs.md +++ b/specifications/provenance-logs.md @@ -18,6 +18,7 @@ This specification is subject to the [Community Specification License 1.0][3] 3. [Specification](#specification) 1. [Features](#features) 2. [Entry](#entry) + 1. [Canonical First Entry](#canonical-first-entry) 3. [Virtual Name Space](#virtual-name-space) 4. [Hierarchical Keys](#hierarchical-keys) 5. [Values](#provenance-values) @@ -138,6 +139,29 @@ the entry's sequence number, the list of mutation operations, the lock scripts for validating the next entry, the unlock script used for validating this entry and the proof data for validating this entry. +#### [Canonical First Entry](#canonical-first-entry) + +The first entry in a provenance log (sequence number 0) MUST establish the +initial state of the key-value store with the following canonical key-value +pairs: + +| Key-Path | Value | Description | +|----------|-------|-------------| +| `/vlad/data` | Binary: first-lock WASM bytes | The WASM validation function binary that is also embedded as the signed message in the VLAD's multisig. This is the data whose hash is verified by the first-lock script. | +| `/vlad/key` | Binary: advertised public key | The public key of the long-lived advertised key pair. The first-lock script verifies that this key hashes to a value baked into the WASM binary. | +| `/pubkey` | Binary: advertised public key | The public key used by subsequent lock scripts for signature verification. By convention this is the same as `/vlad/key` at log creation time but may be rotated independently. | + +The first-lock script (the lock script baked into the VLAD) verifies the +first entry by: +1. Checking that the advertised public key (`/vlad/key`) is the preimage of a + hash value embedded in the WASM binary. +2. Checking a digital signature over the entry using the advertised public key + (`/pubkey`). + +This ensures that only the holder of the advertised key pair can create the +first entry for a given VLAD, binding the log to the advertised key pair +without exposing the ephemeral key used to create the VLAD itself. + ### [Virtual Name Space](#virtual-name-space) Provenance logs are a sequence of entries that each contain an ordered list diff --git a/specifications/vlad.md b/specifications/vlad.md index cc69337..d91ca17 100644 --- a/specifications/vlad.md +++ b/specifications/vlad.md @@ -70,12 +70,6 @@ This document refers to `sigils` the identify a codec or data type. The normative reference for the list of sigils can be found in the [multiformats multicodecs table][5]. -This document refers to `CID` content identifiers. The normative reference for -which can be found in the [multiformats cid specification][6] - -This document refers to `nonce` values. The normative reference for which can -be found in the [multiformats nonce specification][7] - This document refers to `multisig` encoded digital signatures. The normative reference for which can be found in the [multiformats multisig specification][8]. @@ -91,44 +85,36 @@ specification][8]. ## [Specification](#specification) The following diagram shows the overall structure of a VLAD. A VLAD -is identified by the `0x1207` sigil followed by a `nonce` and a `cid`. The -sigil is encoded as a varuint (`0x8724`). +is identified by the `0x1207` sigil followed by a `multisig`. The sigil is +encoded as a varuint (`0x8724`). ``` -vlad sigil cid value - | / - v v -0x8724 - ^ - | - nonce value +vlad sigil multisig value + | / + v v +0x8724 ``` -In some cases, the `varbytes` in the `nonce` are a `multisig` encoded digital -signature over the `cid` value in the VLAD. The following diagram illustrates a -signed VLAD. +The `multisig` is a combined digital signature where the signed message is +embedded within the multisig structure. The message MUST be a valid WebAssembly +binary (i.e. it starts with the WASM magic bytes `\0asm`). This WASM binary is +the validation function used to authorize updates to the provenance log +identified by this VLAD. ``` -vlad sigil cid value - | / - v v -0x8724 - ^ - | - nonce value - - ::= 0xbb24 - ^ - / - nonce sigil - - ::= - ^ ^ - / | - count of variable number - octets of multisig octets + ::= + ^ ^ ^ ^ + / / / | + multisig algorithm varbytes signature + sigil codec encoded WASM attributes + binary (the (includes + combined msg) sig data) ``` +The VLAD is a cryptographic commitment: the digital signature binds the WASM +validation function to the ephemeral signing key used at creation time. Any +modification to the WASM code or the signature invalidates the VLAD. + ## [Annex](#annex) ### [Do Not Use Public Keys as Identifiers](#do-not-use-public-key-as-identifiers) @@ -165,51 +151,48 @@ why the web-of-trust isn't anti-fragile. Given the two primary properties of public keys listed above, it is conceivable that another type of identifer can be constructed with those same properties while also lacking the vulnerabilities and limitations to durability over time. -All we have to do is construct a tuple identifer from a large random value-- -commonly called a `nonce`--and a cryptographic commitment to a validation -function. By combinding content addressable storage and WASM as universally -executable code, any WASM code that validates data using cryptography may be -hashed to create a content address that is both an immutable identifier for -retrieving the WASM code and also a cryptographic verification method to ensure -that the WASM code has not been modified and retains its original form down to -the bit. - -Combining the nonce and the content address (e.g. `cid`) of a WASM validation -function gives us an identifier that is both unique and a cryptographic -commitment to a validation function; the same set of primary properties as -public keys. However this new identifer is not based off of key material and is -not subject to compromise. Any change in the WASM code is detectable and -produces a different identifier. Any change in the nonce also creates a -different identifier. The only way for one of these new identifiers to remain -relevant over time is to remain unchanged. - -This new identifer is called a "Verifiable Long-lived ADdresses" or "VLAD". The -security of the VLAD can be further enhanced by replacing the 256-bit nonce -value with a digital signature--preferably an ECDSA/EdDSA signature for -compactness--over the WASM content address. The resulting signature has the -same amount of entropy as a random nonce but has the added benefit of allowing -the creator of the VLAD to prove they created it. More on this later when we -discuss VLADs as identifiers for provenance logs. - -To reduce the tight binding and fragility of VLADs, they are encoded using the -multiformats standard. A VLAD therefore begins with the multicodec sigil -identifying the data as a VLAD (e.g. `0x1207`) followed by two multiformat -encoded values, a nonce (e.g. `0x123b`) or a multisig (e.g. `0x1239`) followed -by a content addres CID (e.g. `0x01` v1, `0x02` v2, or `0x03` v3). +By combining content addressable storage and WASM as universally executable +code, any WASM code that validates data using cryptography can serve as a +portable, deterministic validation function. A digital signature over that WASM +binary creates a cryptographic commitment that binds the validation function to +a specific creation event without relying on long-lived key material. + +A VLAD ("Verifiable Long-lived ADdress") is constructed by digitally signing a +WASM validation function binary with an ephemeral key pair. The resulting +`multisig` structure contains both the signature and the signed message (the +WASM binary) in a combined format. The VLAD sigil is prepended to this multisig +to form the complete identifier. + +This construction has the same two primary properties as public keys: + +1. The signature provides sufficient entropy to serve as a globally unique + identifier. +2. The embedded WASM binary is a cryptographic commitment to a validation + function that can be used to verify provenance log operations. + +However, unlike a public key identifier, a VLAD is not based on long-lived key +material and is not subject to compromise. The ephemeral key pair used to sign +the VLAD is discarded after creation. Any modification to the WASM code or the +signature invalidates the VLAD. The only way for a VLAD to remain relevant over +time is to remain unchanged. + +To reduce tight binding and fragility, VLADs are encoded using the multiformats +standard. A VLAD begins with the multicodec sigil identifying the data as a +VLAD (e.g. `0x1207`) followed by a multisig (e.g. `0x1239`) encoded digital +signature containing the WASM validation function as the signed message. ### [VLAD Creation](#vlad-creation) -1. Select the WASM validation function they would like to use for validating - PUT operations in the global DHT for updating the mutable forward pointer. - There is likely to be a small well-known set of WASM validation functions - used. The CID of the chosen WASM is the value used in provenance log - creation. -2. Generate an ephemeral cryptographic public key pair. -3. Digitally sign the CID of the WASM code to create the random value for the - VLAD. -4. Encode the digital signature in the multisig multiformat, concatenate the - CID to the multisig and add the VLAD sigil to the front to create a - multiformat encoded VLAD value. +1. Select the WASM validation function to use for validating PUT operations in + the global DHT for updating the mutable forward pointer. There is likely to + be a small well-known set of WASM validation functions used. +2. Generate an ephemeral cryptographic key pair (e.g. Ed25519). +3. Digitally sign the WASM binary with the ephemeral key to produce a combined + multisig (i.e. the WASM binary is embedded as the signed message within the + multisig structure). +4. Prepend the VLAD sigil to the multisig to create the multiformat encoded + VLAD value. +5. Discard the ephemeral key pair. It is not needed after VLAD creation. [0]: https://cryptid.tech [1]: https://github.com/cryptidtech/provenance-specifications/ @@ -217,6 +200,4 @@ by a content addres CID (e.g. `0x01` v1, `0x02` v2, or `0x03` v3). [3]: https://github.com/CommunitySpecification/1.0 [4]: https://github.com/multiformats/unsigned-varint/blob/master/README.md [5]: https://github.com/multiformats/multicodecs/blob/master/table.csv -[6]: https://github.com/multiformats/cid/blob/master/README.md -[7]: https://github.com/cryptidtech/provenance-specifications/blob/main/specifications/nonce.md [8]: https://github.com/cryptidtech/provenance-specifications/blob/main/specifications/multisig.md diff --git a/specifications/wacc-api.md b/specifications/wacc-api.md new file mode 100644 index 0000000..7fceb91 --- /dev/null +++ b/specifications/wacc-api.md @@ -0,0 +1,568 @@ +# WACC VM API Specification (Complete) + +**Version:** 1.0.5 +**Status:** Draft +**Implementation:** bettersign/crates/wacc +© 2024 Cryptid Technologies, Inc. + +## Overview + +This document provides the complete API specification for the Web Assembly Cryptographic Constructs (WACC) VM. The WACC VM is a stack-based virtual machine designed for executing cryptographic verification scripts in provenance systems. + +## Architecture + +### Execution Model + +The WACC VM uses a dual-stack architecture: +- **Parameter Stack (pstack)**: Arguments for operations +- **Return Stack (rstack)**: Results and status values + +### State Model + +The VM operates on two key-value stores: +- **Current State**: Immutable, contains committed data +- **Proposed State**: Immutable, contains data to be verified + +### Context Path + +Operations can be scoped using hierarchical context paths (e.g., `/root/child/grandchild`). + +## API Functions + +All WACC API functions are imported from the "wacc" module and prefixed with underscore. + +### 1. _push + +**Signature:** +```wasm +(import "wacc" "_push" (func $push (param i32 i32) (result i32))) +``` + +**Parameters:** +- `key_ptr` (i32): Pointer to key string in linear memory +- `key_len` (i32): Length of key string + +**Returns:** +- i32: 1 on success, 0 on failure + +**Operation:** +1. Reads key string from linear memory at `[key_ptr, key_ptr + key_len)` +2. Looks up value in current state using the key +3. Pushes value onto parameter stack +4. Returns success/failure + +**Errors:** +- Missing key in current state +- Invalid memory access +- Stack overflow + +**Example:** +```wasm +;; Push value at key "/pubkey" onto stack +(call $push + (i32.const 0) ;; pointer to "/pubkey" string + (i32.const 7)) ;; length of "/pubkey" +``` + +### 2. _pop + +**Signature:** +```wasm +(import "wacc" "_pop" (func $pop (result i32))) +``` + +**Parameters:** None + +**Returns:** +- i32: 1 on success, 0 on failure + +**Operation:** +1. Removes top value from parameter stack +2. Discards the value +3. Returns success/failure + +**Errors:** +- Empty parameter stack + +### 3. _check_eq + +**Signature:** +```wasm +(import "wacc" "_check_eq" (func $check_eq (param i32 i32) (result i32))) +``` + +**Parameters:** +- `key_ptr` (i32): Pointer to key string +- `key_len` (i32): Length of key string + +**Returns:** +- i32: 1 on success, 0 on failure + +**Operation:** +1. Reads key from memory +2. Looks up expected value in current state +3. Peeks at top of parameter stack +4. Compares values for equality (constant-time) +5. If equal: pops stack, increments check_count, returns success +6. If not equal: increments check_count, returns failure + +**Errors:** +- Missing key in current state +- Empty parameter stack +- Type mismatch +- Maximum check count exceeded + +### 4. _check_preimage + +**Signature:** +```wasm +(import "wacc" "_check_preimage" (func $check_preimage (param i32 i32) (result i32))) +``` + +**Parameters:** +- `key_ptr` (i32): Pointer to key string +- `key_len` (i32): Length of key string + +**Returns:** +- i32: 1 on success, 0 on failure + +**Operation:** +1. Reads key from memory +2. Looks up multihash from current state at key +3. Validates hash algorithm is on security whitelist +4. Peeks at preimage data from top of parameter stack +5. Computes hash of preimage using algorithm from stored hash +6. Compares computed hash with stored hash +7. If match: pops preimage from stack, returns success +8. If no match: increments check_count, returns failure + +**Errors:** +- Missing key in current state +- Empty parameter stack +- Invalid multihash format +- Disallowed hash algorithm +- Hash mismatch +- Maximum check count exceeded + +**Security:** +- Only whitelisted hash algorithms are accepted +- Weak algorithms (MD5, SHA-1) are rejected + +### 5. _check_signature + +**Signature:** +```wasm +(import "wacc" "_check_signature" (func $check_signature (param i32 i32 i32 i32) (result i32))) +``` + +**Parameters:** +- `key_ptr` (i32): Pointer to public key identifier string +- `key_len` (i32): Length of public key identifier +- `msg_ptr` (i32): Pointer to message identifier string +- `msg_len` (i32): Length of message identifier + +**Returns:** +- i32: 1 on success, 0 on failure + +**Operation:** +1. Reads public key identifier from memory +2. Looks up multikey from current state +3. Reads message identifier from memory +4. Looks up message from proposed state +5. Peeks at multisig from top of parameter stack +6. Verifies signature using public key and message +7. If valid: pops signature from stack, returns success +8. If invalid: increments check_count, returns failure + +**Errors:** +- Missing public key in current state +- Missing message in proposed state +- Empty parameter stack +- Invalid multikey format +- Invalid multisig format +- Signature verification failed +- Maximum check count exceeded + +**Security:** +- Only whitelisted signature algorithms accepted +- Supports Ed25519, secp256k1, P-256, BLS12-381 + +### 6. _branch + +**Signature:** +```wasm +(import "wacc" "_branch" (func $branch (param i32 i32) (result i32 i32))) +``` + +**Parameters:** +- `key_ptr` (i32): Pointer to branch segment string +- `key_len` (i32): Length of branch segment + +**Returns:** +- i32: Pointer to resulting path string in linear memory +- i32: Length of resulting path string + +**Operation:** +1. Reads branch segment from memory +2. Concatenates current context path with segment +3. Writes result to linear memory (top-down allocation) +4. Returns pointer and length + +**Errors:** +- Invalid memory access +- Path too long (>1024 chars) +- Path too deep (>32 levels) + +**Usage:** +Used to create hierarchical key paths like `/root/child/key`. + +### 7. _log + +**Signature:** +```wasm +(import "wacc" "_log" (func $log (param i32 i32) (result i32))) +``` + +**Parameters:** +- `msg_ptr` (i32): Pointer to log message string +- `msg_len` (i32): Length of message + +**Returns:** +- i32: 1 on success, 0 on failure + +**Operation:** +1. Reads message string from memory +2. Appends message to log buffer with newline +3. Returns success/failure + +**Errors:** +- Invalid memory access +- Log buffer full (>64 KB) + +**Usage:** +Debug and audit logging from WASM scripts. + +### 8. _push_value + +**Signature:** +```wasm +(import "wacc" "_push_value" (func $push_value (param i32 i32) (result i32))) +``` + +**Parameters:** +- `offset` (i32): Pointer to raw bytes in linear memory +- `len` (i32): Length of the byte data + +**Returns:** +- i32: 1 on success, 0 on failure + +**Operation:** +1. Reads raw bytes from linear memory at `[offset, offset + len)` +2. Pushes the bytes as a binary value onto the parameter stack (with an empty hint) +3. Returns success/failure + +**Errors:** +- Invalid memory access +- Stack overflow + +**Usage:** +Used by unlock scripts to push raw byte data (such as a public key or preimage) directly from WASM memory onto the parameter stack, without requiring a key-value store lookup. This is the counterpart to `_push` which reads from the current state store. + +**Example:** +```wasm +;; Push 32 raw bytes from memory offset 0 onto the parameter stack +(call $push_value + (i32.const 0) ;; pointer to bytes in memory + (i32.const 32)) ;; length of bytes +``` + +### 9. _check_preimage_value + +**Signature:** +```wasm +(import "wacc" "_check_preimage_value" (func $check_preimage_value (result i32))) +``` + +**Parameters:** None + +**Returns:** +- i32: 1 on success, 0 on failure + +**Operation:** +1. Pops the expected hash (as a multihash) from the top of the parameter stack +2. Validates the hash algorithm is on the security whitelist +3. Pops the preimage data from the parameter stack +4. Computes the hash of the preimage using the algorithm from the expected hash +5. Compares the computed hash with the expected hash +6. If match: returns success +7. If no match: increments check_count, returns failure + +**Errors:** +- Fewer than 2 values on parameter stack +- Top of stack is not a valid multihash +- Disallowed hash algorithm +- Hash mismatch +- Maximum check count exceeded + +**Security:** +- Only whitelisted hash algorithms are accepted (same whitelist as `_check_preimage`) +- Weak algorithms (MD5, SHA-1) are rejected + +**Usage:** +Used by lock scripts to verify a preimage relationship where both the expected hash and the preimage are provided entirely on the stack (pushed by `_push_value` or `_push`), rather than looking up the expected hash from the current state store. This is useful for the first-lock script which verifies that the advertised public key hashes to the value baked into the WASM binary. + +**Stack layout before call:** +``` + top: [expected multihash] <- popped first + next: [preimage data] <- popped second +``` + +**Comparison with `_check_preimage`:** +- `_check_preimage(key_ptr, key_len)`: Reads the expected hash from the **current state** by key, preimage from the stack. +- `_check_preimage_value()`: Reads **both** the expected hash and the preimage from the **parameter stack**. + +## Stack Values + +### Value Types + +Values on stacks can be: + +1. **Binary (Bin)** + - Contains: `hint` (String), `data` (Arc<[u8]>) + - Used for: Keys, signatures, hashes, binary data + +2. **String (Str)** + - Contains: `hint` (String), `data` (Arc) + - Used for: Text messages, identifiers + +3. **Success** + - Contains: check_count (usize) + - Indicates successful check operation + +4. **Failure** + - Contains: error message (String) + - Indicates failed operation + +### Stack Behavior + +**Parameter Stack:** +- Grows upward (push adds to top) +- Accessed by check operations +- Populated by unlock scripts +- Consumed by lock scripts + +**Return Stack:** +- Accumulates operation results +- Contains Success/Failure markers +- Used to determine overall script success + +## Security Model + +### Resource Limits + +| Resource | Default Limit | Strict Limit | Purpose | +|----------|--------------|--------------|---------| +| Fuel | 1,000,000 | 100,000 | Prevent infinite loops | +| Check Count | 256 | 16 | Limit crypto operations | +| Log Size | 64 KB | 4 KB | Prevent log spam | +| Memory | 4 MB | 1 MB | Prevent exhaustion | +| Path Depth | 32 | 32 | Prevent deep nesting | +| Path Length | 1024 | 1024 | Prevent long paths | + +### Algorithm Whitelisting + +**Allowed Hash Algorithms:** +- SHA-2: sha2-256 (0x12), sha2-512 (0x13), sha2-384 (0x20), sha2-224 (0x1013) +- SHA-3: sha3-256 (0x16), sha3-512 (0x14), sha3-384 (0x15), sha3-224 (0x17) +- Keccak: keccak-256 (0x1b), keccak-512 (0x1d), keccak-384 (0x1c), keccak-224 (0x1a) +- Blake: blake3 (0x1e), blake2b-256 (0xb220), blake2b-512 (0xb240), blake2s-256 (0xb260) + +**Blocked Hash Algorithms:** +- SHA-1 (0x11) - collision attacks +- MD4 (0xd4) - broken +- MD5 (0xd5) - broken + +**Allowed Signature Algorithms:** +- ed25519-pub (0xed) +- secp256k1-pub (0xe7) - Bitcoin/Ethereum +- p256-pub (0x1200) - NIST P-256 +- bls12_381-g1-pub (0xea) + +### Input Validation + +- **Keys**: Maximum 256 characters, no null bytes +- **Paths**: Maximum 1024 characters, maximum 32 depth +- **Memory sizes**: Maximum 16 MB per operation +- **Pointers**: Overflow checked + +## Error Handling + +### Error Types + +1. **VmError** + - MissingContext + - CompilationError + - InstantiationError + - ExecutionError + +2. **ApiError** + - MissingExport + - InvalidParam + - IncorrectNumberOfParams + - MemoryDecodeError + - MemoryAccess + - NoValue + +3. **CryptoError** (domain) + - InvalidHashAlgorithm + - HashMismatch + - SignatureInvalid + +### Return Values + +**Success (1):** +- Operation completed successfully +- For check operations: Success value pushed to rstack + +**Failure (0):** +- Operation failed +- Failure value with error message pushed to rstack + +## Memory Model + +### Linear Memory + +WASM scripts have access to linear memory for string data: +- **Read**: API functions read strings via pointer + length +- **Write**: _branch writes result strings (top-down allocation) +- **Validation**: All accesses are bounds-checked +- **Limit**: Memory size limited by StoreLimits configuration + +### Memory Safety + +- Pointer overflow detection +- Size validation (<= 16 MB per operation) +- Bounds checking on all reads/writes + +## Execution Flow + +### Typical Script Pair + +**Unlock Script:** +1. Pushes data onto parameter stack +2. May log debug information +3. Returns success + +**Lock Script:** +1. Performs check operations on stacked data +2. Verifies cryptographic proofs +3. Returns success only if all checks pass + +### Check Counter + +The `check_count` tracks cryptographic check operations: +- Incremented by: `check_fail()` when checks fail +- Not incremented by: `succeed()` for successful checks +- Pushed with: Success values to track verification state +- Limited to: 256 operations maximum + +## Type Safety + +### Newtype Wrappers + +The implementation uses newtypes for safety: +- `WasmPtr`, `WasmSize` - Memory addresses/sizes +- `FuelAmount` - Computational limits +- `CheckCount` - Operation counters +- `ContextPath` - Hierarchical paths +- `CurrentKey`, `ProposedKey` - Store keys + +## Thread Safety + +### Concurrent Execution + +- **Instance**: Not thread-safe (`!Send`) +- **Module**: Thread-safe (`Send + Sync`) +- **Engine**: Thread-safe (`Send + Sync`) +- **ModuleCache**: Thread-safe (`Send + Sync`) +- **Value**: Thread-safe with Arc (`Send + Sync`) + +### Parallel Patterns + +Multiple instances can run in parallel, each with: +- Separate Context +- Separate storage (Pairs, Stack) +- Shared Engine (via Arc) +- Shared compiled modules (via Arc) + +## Implementation Requirements + +### Mandatory Features + +1. **Fuel Tracking**: MUST limit computational resources +2. **Algorithm Whitelisting**: MUST reject weak algorithms +3. **Resource Limits**: MUST enforce all security limits +4. **Input Validation**: MUST validate all user inputs +5. **Memory Safety**: MUST check all memory operations + +### Optional Features + +1. **Module Caching**: MAY cache compiled modules +2. **Logging**: MAY provide log output +3. **Tracing**: MAY provide debug tracing + +## Conformance + +A conforming WACC VM implementation MUST: +1. Implement all 9 API functions with specified signatures +2. Enforce security limits (fuel, checks, memory, log) +3. Reject weak cryptographic algorithms +4. Validate all inputs (keys, paths, memory operations) +5. Use dual-stack architecture (parameter + return) +6. Support Success/Failure result values +7. Track check_count for verification + +A conforming implementation MAY: +1. Provide additional API functions +2. Use stricter security limits +3. Add additional algorithm support (if secure) +4. Optimize performance (caching, Arc, etc.) + +## Security Considerations + +### Threat Model + +Protected against: +- Resource exhaustion (DoS via infinite loops) +- Memory exhaustion +- Weak cryptography usage +- Path traversal attacks +- Integer overflow +- Buffer overflow + +### Defense Mechanisms + +1. **Mandatory fuel limits** prevent infinite loops +2. **Check count limits** prevent excessive crypto operations +3. **Algorithm whitelisting** prevents weak crypto +4. **Input validation** prevents malformed data +5. **Memory bounds checking** prevents overflows +6. **Constant-time comparisons** prevent timing attacks + +## References + +- [Bitcoin Script](https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki) +- [Multiformats](https://multiformats.io/) +- [Multicodec Table](https://github.com/multiformats/multicodec/blob/master/table.csv) +- [Varuint Encoding](../provenance-specifications/specifications/varuint.md) +- [Multikey Specification](../provenance-specifications/specifications/multikey.md) +- [Multisig Specification](../provenance-specifications/specifications/multisig.md) + +## Example Scripts + +See the implementation repository for complete examples: +- `crates/wacc/examples/log/` - Logging +- `crates/wacc/examples/unlock/` - Unlock scripts +- `crates/wacc/examples/signature_first/` - Signature verification +- `crates/wacc/examples/signature_lock/` - Signature-based locking +- `crates/wacc/examples/wast/` - WebAssembly Text examples From 218100c7b5c154f933fac1bedcdd6145e099ac3b Mon Sep 17 00:00:00 2001 From: Dave Grantham Date: Fri, 27 Feb 2026 01:40:50 -0700 Subject: [PATCH 2/2] update specs to match impl Signed-off-by: Dave Grantham --- specifications/multikey.md | 12 +- specifications/multisig.md | 6 +- specifications/provenance-logs.md | 1855 +++++++++++++++-------------- specifications/wacc-api.md | 38 +- 4 files changed, 953 insertions(+), 958 deletions(-) diff --git a/specifications/multikey.md b/specifications/multikey.md index 77a9a03..3d22cda 100644 --- a/specifications/multikey.md +++ b/specifications/multikey.md @@ -192,16 +192,12 @@ Please file an issue if you have a use case that requires a new attribute type. accumulated key shares while gathring enough shares to recreate the key. **AlgorithmName (0x0c)** -: An arbitrary string name for the algorithm. This is optional and is intended -to support arbitrary and/or non-standard key types. Some implementations may -set this attribute for standard algorithm keys but do not rely upon that. -Interpretation of the algorithm name is application specific. +: Reserved for future use — not currently implemented. Intended to support an +arbitrary string name for the algorithm for non-standard key types. **KeyType (0x0d)** -: An arbitrary numeric key type attribute. This is optional and is intended to -support arbitrary and/or non-standard key types. Some implementations may set -this attribute for standard algorithm keys but do not rely upon that. -Interpretation of the key type is application specific. +: Reserved for future use — not currently implemented. Intended to support an +arbitrary numeric key type attribute for non-standard key types. ### [Secret Keys](#secret-keys) diff --git a/specifications/multisig.md b/specifications/multisig.md index c34b7be..1bcf463 100644 --- a/specifications/multisig.md +++ b/specifications/multisig.md @@ -161,10 +161,8 @@ Multikey, are required to verify the validity of a Multisig signature. accumulate threshold signature shares. **AlgorithmName (0x07)** -: An arbitrary string name for the algorithm. This is optional and is intended -to support arbitrary and/or non-standard signature types. Some implementations -may set this attribute for standard algorithm signatures but do not rely upon -that. Interpretation of the algorithm name is application specific. +: Reserved for future use — not currently implemented. Intended to support an +arbitrary string name for the algorithm for non-standard signature types. ## [Examples](#examples) diff --git a/specifications/provenance-logs.md b/specifications/provenance-logs.md index 29fcdea..546a130 100644 --- a/specifications/provenance-logs.md +++ b/specifications/provenance-logs.md @@ -1,917 +1,938 @@ -# Provenance Logs Specification -**Version: 0.0.1** \ -**Status: Pre-draft** \ -© 2024 Cryptid Technologies, Inc. - -[![](https://img.shields.io/badge/made%20by-Cryptid%20Technologies-gold.svg?style=flat-square)][0] -[![](https://img.shields.io/badge/project-provenance-purple.svg?style=flat-square)][1] -[![](https://img.shields.io/badge/project-multiformats-blue.svg?style=flat-square)][2] - -This specification is subject to the [Community Specification License 1.0][3] - -## Contents -1. [Foreword](#foreword) -2. [Introduction](#introduction) - 1. [Current Status](#current-status) - 2. [Normative References](#normative-references) - 3. [Terms and Definitions](#terms-and-definitions) -3. [Specification](#specification) - 1. [Features](#features) - 2. [Entry](#entry) - 1. [Canonical First Entry](#canonical-first-entry) - 3. [Virtual Name Space](#virtual-name-space) - 4. [Hierarchical Keys](#hierarchical-keys) - 5. [Values](#provenance-values) - 6. [Mutation Operations](#mutation-operations) - 7. [Unlock and Lock Scripts](#unlock-and-lock-scripts) - 1. [Validating a Proposed Entry](#validating-a-proposed-entry) - 2. [Unlock and Lock Script Functions](#unlock-and-lock-script-functions) - 8. [Forking Provenance Logs](#forking-provenance-logs) - 1. [Example Forking Using Delegation](#example-forking-using-delegation) - -## [Foreword](#foreword) - -Attention is drawn to the possibility that some of the elements of this -document may be the subject of patent rights. No party shall not be held -responsible for identifying any or all such patent rights. - -Any trade name used in this document is information given for the convenience -of users and does not constitute an endorsement. - -This document was prepared by Cryptid Technologies, Inc. - -Known patent licensing exclusions are available in the specification’s -repository’s Notices.md file. - -Any feedback or questions on this document should be directed to -[specifications repository][1]. - -THESE MATERIALS ARE PROVIDED “AS IS.” The Contributors and Licensees expressly -disclaim any warranties (express, implied, or otherwise), including implied -warranties of merchantability, non-infringement, fitness for a particular -purpose, or title, related to the materials. The entire risk as to -implementing or otherwise using the materials is assumed by the implementer and -user. IN NO EVENT WILL THE CONTRIBUTORS OR LICENSEES BE LIABLE TO ANY OTHER -PARTY FOR LOST PROFITS OR ANY FORM OF INDIRECT, SPECIAL, INCIDENTAL, OR -CONSEQUENTIAL DAMAGES OF ANY CHARACTER FROM ANY CAUSES OF ACTION OF ANY KIND -WITH RESPECT TO THIS DELIVERABLE OR ITS GOVERNING AGREEMENT, WHETHER BASED ON -BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), OR OTHERWISE, AND WHETHER OR -NOT THE OTHER MEMBER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -## [Introduction](#introduction) - -This specification describes the specification for a hash-linked data structure -that is a cryptographically verifiable provenance log. -Provenance logs are constructed as a singly-linked list of entries that contain -an arbitrarily long, ordered list of mutable operations to a virtual key-value -store as well as a set of cryptographic puzzles that must be solved by the next entry in the list for it to remain valid. -This design ensures delegatable, recoverable and revocable write control over the log. - -### [Current Status](#current-status) - -Provenance Logs are fully defined by this specification and meets all of the needs -of the current set of use cases. If there are new functions needed to meet new -use cases please file a proposal as an issue in this repository. - -### [Normative References](#normative-references) - -This document refers to `nonce` values. The normative reference for which can -be found in the [multiformats nonce specification][5] - -This document refers to `multisig` encoded digital signatures. The normative -reference for which can be found in the [multiformats multisig -specification][6]. - -This document refers to `multikey` encoded cryptography keys. The normative -reference for which can be found in the [multiformats multikey -specification][7]. - -This document refers to the `wacc` VM API. The normative reference for which can -be found in the [wacc_api_specification][8]. - -This document refers to `vlad`s. The normative reference for which can be found -in the [vlad specification][9]. - -This document refers to `cid`s. The normative reference for which can be found -in the [ipfs_content_identifier specification][https://docs.ipfs.tech/concepts/content-addressing/]. - -### [Terms and Definitions](#terms-and-definitions) - -**DAG** -: A directed acyclic graph. - -**LipmaaLinks** -: A cryptographically secure hash of some older -entry in the log, chosen such that there are -short paths between any pair of entries. - -**CBOR** -: Concise Binary Object Representation that is a self-describing -binary data serialization format. - -**Noop** -: No operation - -**Seqno** -: A sequence number represented as a positive integer. - -## [Specification](#specification) - -### [Features](#features) -Provenance Logs support the following list of features: - -* Sequence numbering for detecting forks and erasures. -* [Lipmaa][LIPMAA] links for O(log N) path lengths between any two entries in a - log. -* Forking capability to create child logs with back links to the parent log - forming a DAG of logs. -* Stable, non-key-material identifier to scope/namespace each branch in the - DAG of logs. -* [WACC VM][8] verification script for the next entry is stored inline or - externally in a content-addressable storage. -* Serialization to/from DAG-CBOR for automated retrieval of the entire log. - -### [Entry](#entry) - -Each entry contains the entry's version, the VLAD identifier, -the CID for the previous entry, the CID of the lipmaa predecessor, -the entry's sequence number, the list of mutation operations, the lock -scripts for validating the next entry, the unlock script used for validating -this entry and the proof data for validating this entry. - -#### [Canonical First Entry](#canonical-first-entry) - -The first entry in a provenance log (sequence number 0) MUST establish the -initial state of the key-value store with the following canonical key-value -pairs: - -| Key-Path | Value | Description | -|----------|-------|-------------| -| `/vlad/data` | Binary: first-lock WASM bytes | The WASM validation function binary that is also embedded as the signed message in the VLAD's multisig. This is the data whose hash is verified by the first-lock script. | -| `/vlad/key` | Binary: advertised public key | The public key of the long-lived advertised key pair. The first-lock script verifies that this key hashes to a value baked into the WASM binary. | -| `/pubkey` | Binary: advertised public key | The public key used by subsequent lock scripts for signature verification. By convention this is the same as `/vlad/key` at log creation time but may be rotated independently. | - -The first-lock script (the lock script baked into the VLAD) verifies the -first entry by: -1. Checking that the advertised public key (`/vlad/key`) is the preimage of a - hash value embedded in the WASM binary. -2. Checking a digital signature over the entry using the advertised public key - (`/pubkey`). - -This ensures that only the holder of the advertised key pair can create the -first entry for a given VLAD, binding the log to the advertised key pair -without exposing the ephemeral key used to create the VLAD itself. - -### [Virtual Name Space](#virtual-name-space) - -Provenance logs are a sequence of entries that each contain an ordered list -of mutation operations applied to a virtual key-value pair store. There are -three different possible operations: `update`, `delete`, and `noop`. `update` -creates/modifies the value associated with a key-path. `delete` -removes a key-value pair. `noop` does nothing, but does -"touch" the associated key-path and is important for the lock script execution -system. As each entry in the log is verified and processed, the mutations -contained within are applied to the virtual key-value pair storage. These pairs -have no intrinsic meaning. It is expected that they will have meaning in the -context of the [WACC VM][8] lock and unlock scripts as well as in the -context of applications consuming the logs. - -### [Hierarchical Keys](#hierarchical-keys) - -The keys in the virtual key-value pair system are [UTF-8](https://www.w3schools.com/charsets/ref_html_utf8.asp) strings of printable -characters and are hierarchical in nature. They work similarly to file paths in -Linux in that they use the forward slash `/` character as the separator. All -keys must begin with the `/` character. Multiple `/` characters together are -invalid. Key-paths that end with a `/` are called branch key-paths and -key-paths that do not are called leaf key-paths. It is correct to mentally -think of branches like filesystem folders and leaves like files inside folders. -A branch can contain other branches and/or leaves. - -### [Values](#provenance-values) - -The values in the key-value pair store are self describing and can take one of -three values: *nil*, *str*, or *data*. *nil* is an empty value. *str* -is a UTF-8 text string. *data* is a binary blob. - -### [Mutation Operations](#mutation-operations) - -Let's assume the first entry in a provenance log includes the following -mutation ops: - -```json -"ops": [ - { "noop": [ "/" ] }, - { "update": [ "/name", { "str": [ "foo" ] } ] }, - { "update": [ "/move", { "str": [ "zig" ] } ] }, - { "delete": [ "/zig" ] }, -] -``` - -After validating the first entry and applying its mutations, the virtual -key-value store contains the following: - -```json -{ - "/name": "foo", - "/move": "zig", -} -``` - -Then, assume the second entry contains the following mutation ops: - -```json -"ops": [ - { "update": [ "/name", { "str": [ "bar" ] } ] }, - { "delete": [ "/answer" ] }, - { "update": [ "/move", { "str": [ "zig" ] } ] }, -] -``` - -After validating the second entry and applying its mutations, the virtual -key-value store contains the following: - -```json -{ - "/name": "bar", - "/move": "zig" -} -``` - -## [Unlock and Lock Scripts](#unlock-and-lock-scripts) - -Each entry contains a list of lock scripts and a single unlock script. The set -of lock scripts define the conditions which must be met by the next entry in -the log for it to be valid. The unlock script is the solution to one of the -lock scripts in the previous entry. All scripts are wasm code that -executes inside a [WACC VM][8]. When a script is executed, it is expected -to reference data in a key-value store when executing. Along with the virtual -key-value store built up from the op (operations) in each event, the scripts may also -reference the fields in the event they are a part of (e.g. vlad, seqno, etc). -Scripts can only be unlocked using data associated with their corresponding event. -Lock scripts may reference all data in the key-value -store as well as the data in the entry which it is a part of. - -### [Validating a Proposed Entry](#validating-a-proposed-entry) - -It is critical to the security of the log that the validation of each entry be -done following a very specific order of operations: - -1. Create two empty key-value stores. -2. Load the proposed entry fields (e.g. seqno, vlad, proof, etc) into one - key-value store to establish the proposed state. DO NOT apply the `ops` - mutations from the proposed entry. -3. Load the unlock script from the proposed entry and execute it to initialize - the stack to be used in the lock script execution. -4. Apply the mutations for each entry in the log, from the foot (first) to the - head (last), to the other key-value store to establish the current state of - the log for the lock script execution. -5. Execute each lock script from the current entry in root to leaf order. For - each lock script, clone stack from step 2 and clone the key-value pair store - from step 3 and execute it. -6. Check the top value on the return stack for a SUCCESS value, all other - values indicate a failure. - -### [Unlock and Lock Script Functions](#unlock-and-lock-script-functions) - -The following functions are available for use in lock and unlock scripts: - -**push()** -: Pushes the value associated with the key-path onto the parameter stack. - -**pop()** -: Pops the top value off of the parameter stack. - -**branch()** -: Concatenates the branch key-path with the provided key-path to create a -key-path argument for other functions. When used in lock scripts, the branch -key-path is the key-path the lock script is associated with. When used in -unlock scripts, the branch key-path is always `/`. This function fails if -used in a lock script associated with a leaf. - -**check_eq()** -: Compares the value associated with the key-path with the value on top of the -parameter stack; increments the check counter if it fails. Pops one parameter -off of the parameter stack if it succeeds. - -**check_preimage()** -: Compares the hash associated with the key-path with the hash of the value on -top of the parameter stack; increments the check counter if it fails. Pops -one parameter off of the parameter stack if it succeeds. This function -understands the [Multihash][10] format and is able to generate and -verify preimages using any hash function it supports. - -**check_signature()** -: Verifies the digital signature and message on the parameter stack using the -public key associated with the key-path; increments the check counter if it -fails. Pops two parameters off of the parameter stack if it succeeds. This -function understands the [Multikey][7] and [Multisig][6] -formats and is able to verify any digital signature they support. - -### [Unlock Scripts](#unlock-scripts) - -Unlock scripts are code that compiles to wasm and executed in the [WACC -VM][8]. For historical reasons, each unlock script is a wasm module with a -single exported function called "for_great_justice". - -The unlock script in a proposed new entry references the proof and the other -entry values to provide a solution to one of the lock scripts in the current -head entry of the provenance log. By convention, when an unlock script executes -the key-value store has the following keys populated with the data from the -proposed entry like so: - -``` -─┬─"/" - ╰─┬─ "entry/" - ╰─┬─ "version" - ├─ "vlad" - ├─ "prev" - ├─ "lipmaa" - ├─ "seqno" - ├─ "ops" - ├─ "locks" - ├─ "unlock" - ╰─ "proof" -``` - -An example unlock script for satisfying a `check_signature` lock script looks -like: - -```rust -#[no_mangle] -pub fn for_great_justice() { - // push the serialized Entry as the message - push("/entry/"); - - // push the proof data - push("/entry/proof"); -} -``` - -or in web assembly text: - -```wat -(module - ;; the imported _check_signature function - (import "wacc" "_push" (func $push (param i32 i32) (result i32))) - - ;; the exported "for_great_justice" function to call - (func $main (export "for_great_justice") (param) (result i32) - ;; push("/entry/") - i32.const 0 - i32.const 7 - call $push - - ;; push("/entry/proof") - i32.const 7 - i32.const 12 - call $push - - return - ) - - ;; define the memory to store the string constants in - (memory (export "memory") 1) - - ;; string constant to pass to check_signature - (data (i32.const 0) "/entry/") - (data (i32.const 7) "/entry/proof") -) -``` - -In the example given above, the unlock script pushes the value associated with -the `"/entry/"` key-path which is the compact serialized form of the proposed -entry with a NULL `"/entry/proof"` value. In essence, the value associated with -`"/entry/"` is the serialized entry object that was digitally signed; it is the -message associated with the digital signature associated with the -`"/entry/proof"` key-path. NOTE: this assumes a detached signature but it is -also possible to use a combined signature that contains the signed data, -eliminating the need to push the `"/entry/"` value however this unnecessarily -duplicates data. - -### [Lock Scripts](#lock-scripts) - -Lock scripts are code that compiles to wasm and executed in the [WACC -VM][8]. For historical reasons, each lock script is a wasm module with -a single exported function called "move_every_zig". - -Every entry has a list of key-paths and the associated lock scripts that are -used to validate events with mutations to those parts of the key-value pair -store. Below is an example of what the list of lock scripts looks like: - -```json -"locks": [ - [ "/", "" ], - [ "/delegated/mike/", ""], - [ "/delegated/walker/", ""], - [ "/delegated/dave/", ""], -] -``` - -The [WACC VM][8] execution environment exposes a number of cryptographic -functions to lock and unlock scripts outlined above. To ensure maximum safety, -no lock script has direct access to any of the data in events or the key-value -pair store. Instead, the functions take key-paths as references to data as the -parameters to functions. You've already seen in the examples above how the -`push` function takes a key-path as its argument and the associated value is -pushed onto the parameter stack. - -Each lock script has a key-path that it governs and there may be multiple lock -scripts for each key-path that get executed in the order in which they are -listed in the log event's lock scripts list. If a branch contains -branches/leaves that do not have a lock script associated with them then the -parent branch lock script also governs them and validates any subsequent events -that mutate those parts of the key-value store. - -When a lock script is executed the "context" key-path that is available to the -`branch` function is the longest common key-path in the set of mutation `op` -values in the proposed event. - -#### Calculating the Context Key-Path - -As an example let us assume the proposed event has the following `op` entries: - -```json -"ops": [ - { "update": [ "/forks/001/foo", { "str": [ "bar" ] } ] }, - { "update": [ "/forks/001/move", { "str": [ "zig" ] } ] }, -] -``` - -The "context" key-path is the longest common *branch* key-path in the set of -`op` key-paths. In the example above, the longest common branch key-path is -`"/forks/001/"` so in any lock script that runs to validate this entry, the -`branch` function would concatenate `"/forks/001/"` with the key-path passed -in. For instance, a call to `branch("foo")` would result in the construction of -the `"/forks/001/foo"` key-path. - -Now, let us assume the set of mutation `op` values in the proposed event is -the following: - -```json -"ops": [ - { "update": [ "/forks/001/foo", { "str": [ "bar" ] } ] }, - { "noop": [ "/forks/" ] }, - { "update": [ "/forks/001/move", { "str": [ "zig" ] } ] }, -] -``` - -The longest common branch key-path is `"/forks/"` because of the `noop` -mutation value. This demonstrates the use of the `noop` to "scope" the context -of the proposed events and affect the context key-path for the lock script -execution. NOTE: the `noop` operation is only useful for making the context -key-path closer to the root `"/"` key-path and cannot make it longer. This -effectively elevates the level of proof required to make the proposed event -valid assuming that the closer to the root key-path the context gets, the -closer to the owner—and thus less delegated—the proof capabilities get. - -#### [Lock Script Execution Order](#lock-script-execution-order) - -Here is another example to clarify which lock scripts get executed and in -which order. Assume you have a key-path structure like the following: - -``` -─┬─ "/" - ╰─┬─ "tpubkey" - ├─ "pubkey" - ├─ "hash" - ╰─┬─ "delegated/" - ├─┬─ "mike/" - │ ╰─── "endpoint" - ├─┬─ "walker/" - │ ╰─── "peerid" - ╰─┬─ "dave/" - ╰─── "endpoint" -``` - -And also assume the current entry has the following list of lock scripts -associated with each key-path: - -```json -[ - [ "/", "" ], - [ "/delegated/mike/", ""], - [ "/delegated/walker/", ""], - [ "/delegated/dave/", ""], -] -``` - -If a proposed entry only contains an `op` to `update("/foo")` then the only -lock script that gets executed is the one associated with the `"/"` key-path -because `"/foo"` is in the `"/"` branch. The context key-path in that case is -just `"/"` because that is the longest common key-path in the `op` set. - -If the proposed entry only contains an `op` to -`update("/delegated/mike/endpoint")` then the lock script for `"/"` runs, and if -it fails then the lock script for `"/delegated/mike/"` runs because -`"/delegated/mike/endpoint"` is in the `"/delegated/mike/"` branch. In both cases -the context key-path is `"/delegated/mike/"` since that is the longest common -branch key-path in the `op` set. - -Lock scripts associated with branches closer to the root branch execute first -and if they succeed, no further lock scripts are executed. This allows lock -scripts closer to the root branch to override the lock scripts closer to the -leaves; as it should be. If the provenance log owner wishes to override an -update made to a delegated branch/leaf, their proof takes precedence as long as -it causes a lock script closer to the root branch to succeed. So in this -example, the proposed next entry with proof that satisfies the `"/"` branch -lock script will take precedence over a proposed next entry with a proof that -only satisfies the `"/delegated/mike/"` lock script. - -#### [Example Lock Script](#example-lock-script) - -Assume that applying the mutation ops from foot (i.e. first event) to head -(i.e. most recent) creates the following key-value store state: - -``` -─┬─ "/" - ╰─── "pubkey" -``` - -A lock script may reference the value of the public key like so: - -```rust -#[no_mangle] -pub fn move_every_zig() -> bool { - // check the signature using the data on the stack and the pubkey - check_signature("/pubkey") -} -``` - -or in web assembly text: - -```wat -(module - ;; the imported _check_signature function - (import "wacc" "_check_signature" (func $check_signature (param i32 i32) (result i32))) - - ;; the exported "move_every_zig" function to call - (func $main (export "move_every_zig") (param) (result i32) - i32.const 0 - i32.const 7 - call $check_signature - return - ) - - ;; define the memory to store the string constants in - (memory (export "memory") 1) - - ;; string constant to pass to check_signature - (data (i32.const 0) "/pubkey") -) -``` - -Since lock scripts are associated with a key-path which specifies when/if it is -executed, the above lock script will always reference the public key associated -with the `"/pubkey"` key-path regardless of which key-path it is associated -with because it uses an absolute key-path. The above lock script could also be -rewritten to the following: - -```rust -#[no_mangle] -pub fn move_every_zig() -> bool { - // check the signature using the pubkey in the branch context - check_signature(branch("pubkey")) -} -``` - -This lock script could be associated with the `"/"` key-path or any other -key-path such as `"/foo/"`. In the latter case, the call to `check_signature` -would be passed the key-path `/foo/pubkey`. This is handy for use in delegation -which is explained further down in this document. - -The lock script above executes the `check_signature` function passing it the -key-path referencing the public key to use. The function first peeks at the -value on top of the stack to see if there is a [Multisig][6] encoded -digital signature on the top of the stack and checks that it is the same kind -of signature as the public key. If those match, then the function expects the -stack to have the digital signature on top with the message just below that. It -checks the signature, and if it is valid, it pops both the signature and -message off of the parameter stack and it pushes the `SUCCESS` marker onto the -return stack. - -Lock scripts may themselves have multiple checks that are combined with logical -"and" and "or" operations. In those cases, precedence is established by the -"check counter". The check counter starts at zero and is incremented every time -a `check_*` function is called and fails. When two competing proposed entries -satisfy the same lock script, the one with the _lowest_ check counter value -takes precedence. - -Below is an example of the use of "and" and "or" predicates to construct a lock -script that enforces the proposed entry proof is either a valid threshold -signature or pubkey signature or preimage proof. - -```rust -#[no_mangle] -pub fn move_every_zig() -> bool { - // then check a possible threshold sig... - check_signature("/tpubkey") || // check_count++ - // then check a possible pubkey sig... - check_signature("/pubkey") || // check_count++ - // then the pre-image proof... - check_preimage("/hash") -} -``` - -Here, there are three `check_*` function calls. To enforce precedence in the -checks, every time a `check_*` function is executed and it fails the check -counter is incremented in the WACC VM. If the lock script succeeds, the check -counter value is pushed onto the stack as the payload of the -`SUCCESS(check_counter)` marker. - -Because logical operations in Rust short circuit, in the example lock script -above, the only time the `check_preimage` function will execute is if the -`check_signature("/pubkey")` fails. The only time the -`check_signature("/pubkey")` function will execute is if the -`check_signature("/tpubkey")` function fails. If either `check_signature` -functions fail, their only side effect is to increment the check counter. Then -if the `check_preimage` succeeds, the marker left on the stack will be -`SUCCESS(2)`. If the `check_signature("/pubkey")` succeeds, the marker left on -the stack will be `SUCCESS(1)`. If the `check_signature("/tpubkey")` succeeds, -the marker left on the stack will be `SUCCESS(0)`. If there are two competing -entries and one provides a valid signature and the other provides a valid -preimage, the one with the valid signature takes precedence over the one with -the valid preimage because the check counter is lower for the entry with the -valid signature. - -In the case where competing entries satisfy the same lock script with the same -check count, the entry with the context key-path closest to the `"/"` branch -takes precedence. The solution for resolving a tie is for entry creators to -generate proof with a lower check count or proof that satisfies a lock script -with higher precedence. Single clause lock scripts are a bad idea for this -reason; they leave the door open for unresolvable ties in precedence. - -#### [Delegation](#delegation) - -The lock script mechanism is designed to support delegation. By adding lock -scripts associated with branches/leaves that can be satisfied by proofs -generated by 3rd parties, the owner of a log is delegating the management of -those branches/leaves to those people/services. The precedence and check -counter mechanism is designed to resolve any conflicts between competing -entries, giving a hierarchy of who can override whom when updating a log. - -In the example from above, let us assume we have the following: - -``` -─┬─ "/" - ╰─┬─ "tpubkey" - ├─ "pubkey" - ├─ "hash" - ╰─┬─ "delegated/" - ├─┬─ "mike/" - │ ├─── "pubkey" - │ ╰─── "endpoint" - ╰─┬─ "walker/" - ├─── "pubkey" - ╰─── "peerid" - -``` - -And also assume the current entry has the following list of lock scripts -associated with each key-path: - -```json -[ - [ "/", "" ], - [ "/delegated/", ""], -] -``` - -Taking advantage of the `branch` function, we only need a single lock script to -govern all of the `"/delegated/"` branch: - -```rust -#[no_mangle] -fn lock() -> bool { - check_signature(branch("pubkey")) -} -``` - -If Mike wished to update the value associated with the -`"/delegated/mike/endpoint"`, he would create a new Entry with a single `op`: - -```json -"ops": [ - { "update": [ "/delegated/mike/endpoint", { "str": [ "https://cryptid.tech" ] } ] }, -] -``` - -When validating his proposed entry for my log, first the `"/"` lock script -would run with the branch context of `"/"` and it would fail because Mike does -not possess the capability of creating proof (e.g. a digital signature) over -his entry that validates with the `"/"` lock script. After that fails, the lock -script for the `"/delegated/"` branch executes with the `"/delegated/mike/"` -branch context. If Mike's proposed entry is digitally signed with the secret -key that is associated with the public key value stored under -`"/delegated/mike/pubkey"` then the entry validates and is accepted. - -If Walker wanted to update the value associated with -`"/delegated/walker/peerid"` then he would also create a new proposed event and -digitally sign it with the key pair associated with the public key stored under -`"/delegate/walker/pubkey"` and the same `"/delegated/"` lock script works for -him because the context branch for validating his event is -`"/delegated/walker/"`. - -#### [Forced recovery using precedence](#forced-recovery-using-precedence) - -The purpose of the precedence is to create lock scripts with the hardest to -hack and most secure checks as the top precedences with less secure checks -having lower precedence. Today, the most secure kind of check is a threshold -signature generated through the cooperation of multiple independent -people/services using a quantum-resistant one-time signature schemes such as a -threshold Lamport signature. If the lock scripts have a threshold check that -takes precedence over a public key signature that in turn takes precedence over -a preimage check, then if a password (i.e. preimage) is compromised and the -attacker creates a new entry using a preimage proof, the rightful owner of the -log can then create a competing entry with the same sequence number using a -signature proof. The log "protocol" demands that the higher precedence entry -to be chosen as the next valid entry and not the one created by the attacker -who guessed your password. - -Most importantly, if a public key pair is compromised and an attacker attempts -a takeover by creating an entry using a signature proof, the rightful owner can -then contact their friends—or a threshold recovery service—and have them create -a threshold signature over a new entry that takes precedence over the -attacker's entry. The new entry with the threshold signature can then contain a -key rotation and key revocation mutation for the virtual key-value store -marking the compromised key as revoked as well as establishing a new valid -public keypair all without breaking the chain of trust in the log. - -You can think of this recovery scenario as a digital "social recovery" where -your friends collaborate to generate the threshold signature to recover your -control over your log. This can be applied in a number of real-world scenarios -such as board control over a corporation's identity log, heirs collaborating -with the deceased's counsel to take over the deceased's logs, or even parents -co-signing with their minor children to update the children's log for some -reason. - -One especially useful application is empowering maintainers of a Git repository -with full identity and access management (IAM) capabilities. By requiring that -the `"/"` branch lock script have a `check_signature("/maintainers")` threshold -signature check as the highest precedence, then the threshold group associated -with the "maintainers" threshold public key has full control over every -key-value pair in the provenance log. Maintainers can force rotate to a new -signing key for a contributor to recover to a known-good key. They can also -force rotate a contributor's signing key to NULL and remove their ability to -contribute to the repository. They can delegate branches and leaves in -contributors' provenance logs to enable a repository-wide service to update the -contributors' logs as needed. In short, a design like this gives the -maintainers of the repository full control over the IAM for contributors to the -repository all without a centralized server/service such as Github or Gitlab. - -## [Forking Provenance Logs](#forking-provenance-logs) - -It is possible to fork provenance logs by into any number of child logs. Child -logs always start with an entry that has a sequence number of `0` and a prev -CID that points at the entry in the parent log from which the child log is -forked. The lock script in the parent log entry is used as the lock script to -validate the first entry in the child log. By convention, along with the prev -CID pointing to the parent, the first entry in the child log must also contain -a mutation `op` that updates the VLAD of the parent to the `"/parent/"` -key-path. The parent VLAD is useful for when the parent log moves from one -content addressable storage to another. As long as a VLAD to CID mapping record -exists somewhere, then the CID can be resolved to the correct entry in the -parent log. - -### [Example Forking Using Delegation](#example-forking-using-delegation) - -Typically, the parent log maintains information about its child forks under the -`"/forks/"` branch. Using the delegation and branch mechanisms, it is trivial -to manage forking using a separate lock script assigned to the `"/forks/"` -key-path: - -```rust -#[no_mangle] -pub fn move_every_zig() -> bool { - // forking the parent be done by whomever can sign with "/forks/pubkey" - check_signature(branch("pubkey")) || - - // check the validity of the first entry of the child log - (check_eq(branch("vlad")) && check_signature(branch("pubkey"))) -} -``` - -The process of forking a provenance log takes two steps. The first step is -recording the information about the child log in the parent log and the -second step is publishing the child log. By convention, each child log's -information is stored under its own branch under `"/forks/"`. The child's -branch name is arbitrary but, again by convention, under its branch there is a -`"vlad"` leaf with the child's VLAD, a `"pubkey"` leaf with the child's first -advertised pubkey that was also used to sign its first entry and the CID in its -VLAD. - -When creating a child log fork, we create a new entry in the parent log with -the following mutation `op` values: - -```json -"ops": [ - { "noop": [ "/forks/" ] }, - { "update": [ "/forks/child1/vlad", { "bin": [ ] } ] }, - { "update": [ "/forks/child1/pubkey", { "bin": [ ] } ] }, -] -``` - -This new entry is signed using the key pair associated with `"/forks/pubkey"`. -The `noop` mutation is used to set the context key-path to `"/forks/"` by -including it the `op` mutation set of key-paths. It is the longest common -key-path and thus becomes the context key-path for the `branch` function when -running it to validate the new event in the parent log. - -When the lock script associated with `"/forks/"` executes, the first -`check_signature` call passes and the lock script exits with `SUCCESS(0)` on -the return stack. - -Then the first entry of the child log is created with the child VLAD as its -VLAD, the prev CID set to the CID of the parent event we just created. It must -include an `op` mutation to record the parent VLAD in the -`"/forks/child1/parent"` leaf of the child's key-value store: - -```json -"ops": [ - { "update": [ "/forks/child1/parent", { "bin": [ ] } ] }, - { "update": [ "/forks/child1/pubkey", { "bin": [ ] } ] }, -] -``` - -The additional `"/forks/child1/pubkey"` in the child log records the signing -key in the child in such a way that it doesn't disrupt the delegation and -forking lock script execution but allows for the child log to have its own -lock scripts and validate the next entry in the log. The only rule is that the -child's initial entry can only have `op` mutations under the `"/forks/child1/"` -key-path to ensure that the lock script from the parent executes correctly and -validates the child's first entry. - -The unlock script of the first entry in the child log must be like the -following: - -```rust -#[no_mangle] -pub fn unlock() { - // push the entry values - push("/entry/"); - - // push the signature created using the /forks/child1/pubkey keypair - push("/entry/proof"); - - // push the vlad - push("/entry/vlad"); -} -``` - -When validating the first entry in the first child log, the unlock script -pushes the serialized entry, the ephemeral signature over the entry as well as -the log's VLAD value. This puts the parameter stack into the following state: - -``` - ┌──────────────────┐ -top → │ <"/entry/vlad"> │ ← child vlad - ├──────────────────┤ - │ <"/entry/proof"> │ ← digital signature - ├──────────────────┤ - │ <"/entry/"> │ ← signed message - ├──────────────────┤ - │ ┆ │ - ┆ ┆ -``` - -The first entry of the child log must be signed using the key pair associated -with the pubkey recorded in the parent log under the `"/forks/child1/pubkey"` -leaf. When the first entry is validated, the lock script from the parent -associated with the parent's `"/forks/"` key-path will execute because the -first entry in the child log only mutates key-paths under `"/forks/"`. The -context path this time will be `"/forks/child1/"` since that is the longest -common branch key-path in the `op` mutation set in the child's first entry. - -When the parent log entry's lock script executes, the following steps are -executed: - -1. The `check_signature(branch("pubkey"))` fails because it sees a VLAD on top - of the stack and not a signature. -2. The `check_eq(branch("vlad"))` succeeds because the `<"/forks/child1/vlad">` - value in the parent matches the "VLAD" value pushed on the stack by the - child unlock script. It pops the VLAD parameter off of the parameter stack. -3. The `check_signature(branch("pubkey"))` succeeds because the - `<"/forks/child1/pubkey">` public key in the parent validates the signature - `<"/entry/proof">` and message `<"/entry/">` values pushed on the stack by the - child unlock script. It pops both off of the parameter stack. -4. A `SUCCESS(1)` marker is pushed onto the results stack because the initial - `check_signature` function failed, incrementing the check counter once. - -The initial entry in the child log should also set up it's own lock scripts -for governing its key-value store. The data recorded under the -`"/forks/child1/"` branch in the child's key-value store can be used with lock -scripts in the child to validate the second entry in the child provenance log -which can then remove the `"/forks/child1/"` branch if so desired. - -[0]: https://cryptid.tech -[1]: https://github.com/cryptidtech/provenance-specifications/ -[2]: https://github.com/multiformats/multiformats -[3]: https://github.com/CommunitySpecification/1.0 -[4]: https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki -[5]: https://github.com/cryptidtech/provenance-specifications/blob/main/specifications/nonce.md -[6]: https://github.com/cryptidtech/provenance-specifications/blob/main/specifications/multisig.md -[7]: https://github.com/cryptidtech/provenance-specifications/blob/main/specifications/multikey.md -[8]: https://github.com/cryptidtech/provenance-specifications/blob/main/specifications/wacc.md -[9]: https://github.com/cryptidtech/provenance-specifications/blob/main/specifications/vlad.md -[10]: https://github.com/cryptidtech/multihash.git - -[LIPMAA]: https://github.com/AljoschaMeyer/bamboo/blob/master/README.md#links-and-entry-verification \ No newline at end of file +# Provenance Logs Specification +**Version: 0.0.1** \ +**Status: Pre-draft** \ +© 2024 Cryptid Technologies, Inc. + +[![](https://img.shields.io/badge/made%20by-Cryptid%20Technologies-gold.svg?style=flat-square)][0] +[![](https://img.shields.io/badge/project-provenance-purple.svg?style=flat-square)][1] +[![](https://img.shields.io/badge/project-multiformats-blue.svg?style=flat-square)][2] + +This specification is subject to the [Community Specification License 1.0][3] + +## Contents +1. [Foreword](#foreword) +2. [Introduction](#introduction) + 1. [Current Status](#current-status) + 2. [Normative References](#normative-references) + 3. [Terms and Definitions](#terms-and-definitions) +3. [Specification](#specification) + 1. [Features](#features) + 2. [Entry](#entry) + 1. [Canonical First Entry](#canonical-first-entry) + 3. [Virtual Name Space](#virtual-name-space) + 4. [Hierarchical Keys](#hierarchical-keys) + 5. [Values](#provenance-values) + 6. [Mutation Operations](#mutation-operations) + 7. [Unlock and Lock Scripts](#unlock-and-lock-scripts) + 1. [Validating a Proposed Entry](#validating-a-proposed-entry) + 2. [Unlock and Lock Script Functions](#unlock-and-lock-script-functions) + 8. [Forking Provenance Logs](#forking-provenance-logs) + 1. [Example Forking Using Delegation](#example-forking-using-delegation) + +## [Foreword](#foreword) + +Attention is drawn to the possibility that some of the elements of this +document may be the subject of patent rights. No party shall not be held +responsible for identifying any or all such patent rights. + +Any trade name used in this document is information given for the convenience +of users and does not constitute an endorsement. + +This document was prepared by Cryptid Technologies, Inc. + +Known patent licensing exclusions are available in the specification’s +repository’s Notices.md file. + +Any feedback or questions on this document should be directed to +[specifications repository][1]. + +THESE MATERIALS ARE PROVIDED “AS IS.” The Contributors and Licensees expressly +disclaim any warranties (express, implied, or otherwise), including implied +warranties of merchantability, non-infringement, fitness for a particular +purpose, or title, related to the materials. The entire risk as to +implementing or otherwise using the materials is assumed by the implementer and +user. IN NO EVENT WILL THE CONTRIBUTORS OR LICENSEES BE LIABLE TO ANY OTHER +PARTY FOR LOST PROFITS OR ANY FORM OF INDIRECT, SPECIAL, INCIDENTAL, OR +CONSEQUENTIAL DAMAGES OF ANY CHARACTER FROM ANY CAUSES OF ACTION OF ANY KIND +WITH RESPECT TO THIS DELIVERABLE OR ITS GOVERNING AGREEMENT, WHETHER BASED ON +BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), OR OTHERWISE, AND WHETHER OR +NOT THE OTHER MEMBER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## [Introduction](#introduction) + +This specification describes the specification for a hash-linked data structure +that is a cryptographically verifiable provenance log. +Provenance logs are constructed as a singly-linked list of entries that contain +an arbitrarily long, ordered list of mutable operations to a virtual key-value +store as well as a set of cryptographic puzzles that must be solved by the next entry in the list for it to remain valid. +This design ensures delegatable, recoverable and revocable write control over the log. + +### [Current Status](#current-status) + +Provenance Logs are fully defined by this specification and meets all of the needs +of the current set of use cases. If there are new functions needed to meet new +use cases please file a proposal as an issue in this repository. + +### [Normative References](#normative-references) + +This document refers to `nonce` values. The normative reference for which can +be found in the [multiformats nonce specification][5] + +This document refers to `multisig` encoded digital signatures. The normative +reference for which can be found in the [multiformats multisig +specification][6]. + +This document refers to `multikey` encoded cryptography keys. The normative +reference for which can be found in the [multiformats multikey +specification][7]. + +This document refers to the `wacc` VM API. The normative reference for which can +be found in the [wacc_api_specification][8]. + +This document refers to `vlad`s. The normative reference for which can be found +in the [vlad specification][9]. + +This document refers to `cid`s. The normative reference for which can be found +in the [ipfs_content_identifier specification][https://docs.ipfs.tech/concepts/content-addressing/]. + +### [Terms and Definitions](#terms-and-definitions) + +**DAG** +: A directed acyclic graph. + +**LipmaaLinks** +: A cryptographically secure hash of some older +entry in the log, chosen such that there are +short paths between any pair of entries. + +**CBOR** +: Concise Binary Object Representation that is a self-describing +binary data serialization format. + +**Noop** +: No operation + +**Seqno** +: A sequence number represented as a positive integer. + +## [Specification](#specification) + +### [Features](#features) +Provenance Logs support the following list of features: + +* Sequence numbering for detecting forks and erasures. +* [Lipmaa][LIPMAA] links for O(log N) path lengths between any two entries in a + log. +* Forking capability to create child logs with back links to the parent log + forming a DAG of logs. +* Stable, non-key-material identifier to scope/namespace each branch in the + DAG of logs. +* [WACC VM][8] verification script for the next entry is stored inline or + externally in a content-addressable storage. +* Serialization to/from DAG-CBOR for automated retrieval of the entire log. + +### [Entry](#entry) + +Each entry contains the entry's version, the VLAD identifier, +the CID for the previous entry, the CID of the lipmaa predecessor, +the entry's sequence number, the list of mutation operations, the lock +scripts for validating the next entry, the unlock script used for validating +this entry and the named proofs data (`proofs: BTreeMap>`) for +validating this entry. + +#### [Canonical First Entry](#canonical-first-entry) + +The first entry in a provenance log (sequence number 0) MUST establish the +initial state of the key-value store with the following canonical key-value +pairs: + +| Key-Path | Value | Description | +|----------|-------|-------------| +| `/vlad/data` | Binary: first-lock WASM bytes | The WASM validation function binary that is also embedded as the signed message in the VLAD's multisig. This is the data whose hash is verified by the first-lock script. | +| `/vlad/key` | Binary: advertised public key | The public key of the long-lived advertised key pair. The first-lock script verifies that this key hashes to a value baked into the WASM binary. | +| `/keys/primary` | Binary: primary public key | The primary public key used by subsequent lock scripts for signature verification. By convention this is the same as `/vlad/key` at log creation time but may be rotated independently. | +| `/keys/recovery` | Binary: recovery public key | The recovery public key used for higher-precedence recovery operations. Lock scripts check this key first (check_count=0), giving recovery key signatures precedence over primary key signatures. | +| `/proofs/primary` | Binary: primary signature | The digital signature over the entry created with the primary key pair. | +| `/proofs/recovery` | Binary: recovery signature | The digital signature over the entry created with the recovery key pair (when present). | +| `/peerid` | Binary: peer identifier | The peer identifier associated with this log. | + +The first-lock script (the lock script baked into the VLAD) verifies the +first entry by: +1. Checking that the advertised public key (`/vlad/key`) is the preimage of a + hash value embedded in the WASM binary. +2. Checking a digital signature over the entry using the primary public key + (`/keys/primary`). + +This ensures that only the holder of the advertised key pair can create the +first entry for a given VLAD, binding the log to the advertised key pair +without exposing the ephemeral key used to create the VLAD itself. + +### [Virtual Name Space](#virtual-name-space) + +Provenance logs are a sequence of entries that each contain an ordered list +of mutation operations applied to a virtual key-value pair store. There are +three different possible operations: `update`, `delete`, and `noop`. `update` +creates/modifies the value associated with a key-path. `delete` +removes a key-value pair. `noop` does nothing, but does +"touch" the associated key-path and is important for the lock script execution +system. As each entry in the log is verified and processed, the mutations +contained within are applied to the virtual key-value pair storage. These pairs +have no intrinsic meaning. It is expected that they will have meaning in the +context of the [WACC VM][8] lock and unlock scripts as well as in the +context of applications consuming the logs. + +### [Hierarchical Keys](#hierarchical-keys) + +The keys in the virtual key-value pair system are [UTF-8](https://www.w3schools.com/charsets/ref_html_utf8.asp) strings of printable +characters and are hierarchical in nature. They work similarly to file paths in +Linux in that they use the forward slash `/` character as the separator. All +keys must begin with the `/` character. Multiple `/` characters together are +invalid. Key-paths that end with a `/` are called branch key-paths and +key-paths that do not are called leaf key-paths. It is correct to mentally +think of branches like filesystem folders and leaves like files inside folders. +A branch can contain other branches and/or leaves. + +### [Values](#provenance-values) + +The values in the key-value pair store are self describing and can take one of +three values: *nil*, *str*, or *data*. *nil* is an empty value. *str* +is a UTF-8 text string. *data* is a binary blob. + +### [Mutation Operations](#mutation-operations) + +Let's assume the first entry in a provenance log includes the following +mutation ops: + +```json +"ops": [ + { "noop": [ "/" ] }, + { "update": [ "/name", { "str": [ "foo" ] } ] }, + { "update": [ "/move", { "str": [ "zig" ] } ] }, + { "delete": [ "/zig" ] }, +] +``` + +After validating the first entry and applying its mutations, the virtual +key-value store contains the following: + +```json +{ + "/name": "foo", + "/move": "zig", +} +``` + +Then, assume the second entry contains the following mutation ops: + +```json +"ops": [ + { "update": [ "/name", { "str": [ "bar" ] } ] }, + { "delete": [ "/answer" ] }, + { "update": [ "/move", { "str": [ "zig" ] } ] }, +] +``` + +After validating the second entry and applying its mutations, the virtual +key-value store contains the following: + +```json +{ + "/name": "bar", + "/move": "zig" +} +``` + +## [Unlock and Lock Scripts](#unlock-and-lock-scripts) + +Each entry contains a list of lock scripts and a single unlock script. The set +of lock scripts define the conditions which must be met by the next entry in +the log for it to be valid. The unlock script is the solution to one of the +lock scripts in the previous entry. All scripts are wasm code that +executes inside a [WACC VM][8]. When a script is executed, it is expected +to reference data in a key-value store when executing. Along with the virtual +key-value store built up from the op (operations) in each event, the scripts may also +reference the fields in the event they are a part of (e.g. vlad, seqno, etc). +Scripts can only be unlocked using data associated with their corresponding event. +Lock scripts may reference all data in the key-value +store as well as the data in the entry which it is a part of. + +### [Validating a Proposed Entry](#validating-a-proposed-entry) + +It is critical to the security of the log that the validation of each entry be +done following a very specific order of operations: + +1. Create two empty key-value stores. +2. Load the proposed entry fields (e.g. seqno, vlad, proofs, etc) into one + key-value store to establish the proposed state. DO NOT apply the `ops` + mutations from the proposed entry. +3. Load the unlock script from the proposed entry and execute it to initialize + the stack to be used in the lock script execution. +4. Apply the mutations for each entry in the log, from the foot (first) to the + head (last), to the other key-value store to establish the current state of + the log for the lock script execution. +5. Execute each lock script from the current entry in root to leaf order. For + each lock script, clone stack from step 2 and clone the key-value pair store + from step 3 and execute it. +6. Check the top value on the return stack for a SUCCESS value, all other + values indicate a failure. + +### [Unlock and Lock Script Functions](#unlock-and-lock-script-functions) + +The following functions are available for use in lock and unlock scripts: + +**push()** +: Pushes the value associated with the key-path onto the parameter stack. + +**push_value()** +: Pushes raw byte data directly from WASM memory onto the parameter stack, +without requiring a key-value store lookup. This is the counterpart to `push` +which reads from the current state store. + +**branch()** +: Concatenates the branch key-path with the provided key-path to create a +key-path argument for other functions. When used in lock scripts, the branch +key-path is the key-path the lock script is associated with. When used in +unlock scripts, the branch key-path is always `/`. This function fails if +used in a lock script associated with a leaf. + +**check_eq()** +: Compares the value associated with the key-path with the value on top of the +parameter stack; increments the check counter if it fails. Pops one parameter +off of the parameter stack if it succeeds. + +**check_preimage()** +: Compares the hash associated with the key-path with the hash of the value on +top of the parameter stack; increments the check counter if it fails. Pops +one parameter off of the parameter stack if it succeeds. This function +understands the [Multihash][10] format and is able to generate and +verify preimages using any hash function it supports. + +**check_preimage_value()** +: Pops the expected hash (as a multihash) and the preimage data from the +parameter stack, then verifies that the preimage hashes to the expected value. +Unlike `check_preimage`, both values come entirely from the stack rather than +looking up the expected hash from the current state store. This is useful for +the first-lock script which verifies that the advertised public key hashes to +the value baked into the WASM binary. + +**check_signature(, )** +: Verifies the digital signature on the parameter stack using the public key +associated with the key-path and the message associated with the msg-path; +increments the check counter if it fails. Pops the signature off of the +parameter stack if it succeeds. The public key is read from the current state +and the message is read from the proposed state. This function understands the +[Multikey][7] and [Multisig][6] formats and is able to verify any digital +signature they support. + +**log()** +: Appends a debug/audit message to the VM log buffer. This is used for +debugging and auditing script execution. + +### [Unlock Scripts](#unlock-scripts) + +Unlock scripts are code that compiles to wasm and executed in the [WACC +VM][8]. For historical reasons, each unlock script is a wasm module with a +single exported function called "for_great_justice". + +The unlock script in a proposed new entry references the proof and the other +entry values to provide a solution to one of the lock scripts in the current +head entry of the provenance log. By convention, when an unlock script executes +the key-value store has the following keys populated with the data from the +proposed entry like so: + +``` +─┬─"/" + ├─┬─ "entry/" + │ ╰─┬─ "version" + │ ├─ "vlad" + │ ├─ "prev" + │ ├─ "lipmaa" + │ ├─ "seqno" + │ ├─ "ops" + │ ├─ "locks" + │ ╰─ "unlock" + ╰─┬─ "proofs/" + ╰─── (e.g. "primary", "recovery") +``` + +An example unlock script for satisfying a `check_signature` lock script looks +like: + +```rust +#[no_mangle] +pub fn for_great_justice() { + // push the proof signature from the named proofs map + push("/proofs/primary"); +} +``` + +or in web assembly text: + +```wat +(module + ;; the imported _push function + (import "wacc" "_push" (func $push (param i32 i32) (result i32))) + + ;; the exported "for_great_justice" function to call + (func $main (export "for_great_justice") (param) (result i32) + ;; push("/proofs/primary") + i32.const 0 + i32.const 16 + call $push + + return + ) + + ;; define the memory to store the string constants in + (memory (export "memory") 1) + + ;; string constant for the named proof path + (data (i32.const 0) "/proofs/primary") +) +``` + +In the example given above, the unlock script pushes the digital signature +from the named proofs map at `"/proofs/primary"` onto the parameter stack. +The lock script's `check_signature(key_path, msg_path)` call reads the +verification key from the current state at `key_path` and the message from +the proposed state at `msg_path` (typically `"/entry/"`, the serialized +entry with proofs cleared). NOTE: this assumes a detached signature but it is +also possible to use a combined signature that contains the signed data. + +### [Lock Scripts](#lock-scripts) + +Lock scripts are code that compiles to wasm and executed in the [WACC +VM][8]. For historical reasons, each lock script is a wasm module with +a single exported function called "move_every_zig". + +Every entry has a list of key-paths and the associated lock scripts that are +used to validate events with mutations to those parts of the key-value pair +store. Below is an example of what the list of lock scripts looks like: + +```json +"locks": [ + [ "/", "" ], + [ "/delegated/mike/", ""], + [ "/delegated/walker/", ""], + [ "/delegated/dave/", ""], +] +``` + +The [WACC VM][8] execution environment exposes a number of cryptographic +functions to lock and unlock scripts outlined above. To ensure maximum safety, +no lock script has direct access to any of the data in events or the key-value +pair store. Instead, the functions take key-paths as references to data as the +parameters to functions. You've already seen in the examples above how the +`push` function takes a key-path as its argument and the associated value is +pushed onto the parameter stack. + +Each lock script has a key-path that it governs and there may be multiple lock +scripts for each key-path that get executed in the order in which they are +listed in the log event's lock scripts list. If a branch contains +branches/leaves that do not have a lock script associated with them then the +parent branch lock script also governs them and validates any subsequent events +that mutate those parts of the key-value store. + +When a lock script is executed the "context" key-path that is available to the +`branch` function is the longest common key-path in the set of mutation `op` +values in the proposed event. + +#### Calculating the Context Key-Path + +As an example let us assume the proposed event has the following `op` entries: + +```json +"ops": [ + { "update": [ "/forks/001/foo", { "str": [ "bar" ] } ] }, + { "update": [ "/forks/001/move", { "str": [ "zig" ] } ] }, +] +``` + +The "context" key-path is the longest common *branch* key-path in the set of +`op` key-paths. In the example above, the longest common branch key-path is +`"/forks/001/"` so in any lock script that runs to validate this entry, the +`branch` function would concatenate `"/forks/001/"` with the key-path passed +in. For instance, a call to `branch("foo")` would result in the construction of +the `"/forks/001/foo"` key-path. + +Now, let us assume the set of mutation `op` values in the proposed event is +the following: + +```json +"ops": [ + { "update": [ "/forks/001/foo", { "str": [ "bar" ] } ] }, + { "noop": [ "/forks/" ] }, + { "update": [ "/forks/001/move", { "str": [ "zig" ] } ] }, +] +``` + +The longest common branch key-path is `"/forks/"` because of the `noop` +mutation value. This demonstrates the use of the `noop` to "scope" the context +of the proposed events and affect the context key-path for the lock script +execution. NOTE: the `noop` operation is only useful for making the context +key-path closer to the root `"/"` key-path and cannot make it longer. This +effectively elevates the level of proof required to make the proposed event +valid assuming that the closer to the root key-path the context gets, the +closer to the owner—and thus less delegated—the proof capabilities get. + +#### [Lock Script Execution Order](#lock-script-execution-order) + +Here is another example to clarify which lock scripts get executed and in +which order. Assume you have a key-path structure like the following: + +``` +─┬─ "/" + ╰─┬─ "keys/" + │ ├─── "primary" + │ ╰─── "recovery" + ├─ "hash" + ╰─┬─ "delegated/" + ├─┬─ "mike/" + │ ╰─── "endpoint" + ├─┬─ "walker/" + │ ╰─── "peerid" + ╰─┬─ "dave/" + ╰─── "endpoint" +``` + +And also assume the current entry has the following list of lock scripts +associated with each key-path: + +```json +[ + [ "/", "" ], + [ "/delegated/mike/", ""], + [ "/delegated/walker/", ""], + [ "/delegated/dave/", ""], +] +``` + +If a proposed entry only contains an `op` to `update("/foo")` then the only +lock script that gets executed is the one associated with the `"/"` key-path +because `"/foo"` is in the `"/"` branch. The context key-path in that case is +just `"/"` because that is the longest common key-path in the `op` set. + +If the proposed entry only contains an `op` to +`update("/delegated/mike/endpoint")` then the lock script for `"/"` runs, and if +it fails then the lock script for `"/delegated/mike/"` runs because +`"/delegated/mike/endpoint"` is in the `"/delegated/mike/"` branch. In both cases +the context key-path is `"/delegated/mike/"` since that is the longest common +branch key-path in the `op` set. + +Lock scripts associated with branches closer to the root branch execute first +and if they succeed, no further lock scripts are executed. This allows lock +scripts closer to the root branch to override the lock scripts closer to the +leaves; as it should be. If the provenance log owner wishes to override an +update made to a delegated branch/leaf, their proof takes precedence as long as +it causes a lock script closer to the root branch to succeed. So in this +example, the proposed next entry with proof that satisfies the `"/"` branch +lock script will take precedence over a proposed next entry with a proof that +only satisfies the `"/delegated/mike/"` lock script. + +#### [Example Lock Script](#example-lock-script) + +Assume that applying the mutation ops from foot (i.e. first event) to head +(i.e. most recent) creates the following key-value store state: + +``` +─┬─ "/" + ╰─┬─ "keys/" + ├─── "primary" + ╰─── "recovery" +``` + +A lock script may reference the value of the public key like so: + +```rust +#[no_mangle] +pub fn move_every_zig() -> bool { + // check the signature using the key and message paths + check_signature("/keys/primary", "/entry/") +} +``` + +or in web assembly text: + +```wat +(module + ;; the imported _check_signature function (key_ptr, key_len, msg_ptr, msg_len) + (import "wacc" "_check_signature" (func $check_signature (param i32 i32 i32 i32) (result i32))) + + ;; the exported "move_every_zig" function to call + (func $main (export "move_every_zig") (param) (result i32) + i32.const 0 + i32.const 13 + i32.const 13 + i32.const 7 + call $check_signature + return + ) + + ;; define the memory to store the string constants in + (memory (export "memory") 1) + + ;; string constants to pass to check_signature + (data (i32.const 0) "/keys/primary") + (data (i32.const 13) "/entry/") +) +``` + +Since lock scripts are associated with a key-path which specifies when/if it is +executed, the above lock script will always reference the public key associated +with the `"/keys/primary"` key-path regardless of which key-path it is associated +with because it uses an absolute key-path. The above lock script could also be +rewritten to the following: + +```rust +#[no_mangle] +pub fn move_every_zig() -> bool { + // check the signature using the key in the branch context + check_signature(branch("keys/primary"), "/entry/") +} +``` + +This lock script could be associated with the `"/"` key-path or any other +key-path such as `"/foo/"`. In the latter case, the call to `check_signature` +would be passed the key-path `/foo/keys/primary`. This is handy for use in +delegation which is explained further down in this document. + +The lock script above executes the `check_signature` function passing it the +key-path referencing the public key to use and the message path. The function +reads the [Multikey][7] public key from the current state at the key-path, +reads the message from the proposed state at the msg-path, and peeks at the +[Multisig][6] encoded digital signature on top of the parameter stack. It +checks the signature, and if it is valid, it pops the signature off of the +parameter stack and pushes the `SUCCESS` marker onto the return stack. + +Lock scripts may themselves have multiple checks that are combined with logical +"and" and "or" operations. In those cases, precedence is established by the +"check counter". The check counter starts at zero and is incremented every time +a `check_*` function is called and fails. When two competing proposed entries +satisfy the same lock script, the one with the _lowest_ check counter value +takes precedence. + +Below is an example of the use of "and" and "or" predicates to construct a lock +script that enforces the proposed entry proof is either a valid recovery key +signature, primary key signature, or preimage proof. + +```rust +#[no_mangle] +pub fn move_every_zig() -> bool { + // then check a possible recovery key sig... + check_signature("/keys/recovery", "/entry/") || // check_count++ + // then check a possible primary key sig... + check_signature("/keys/primary", "/entry/") || // check_count++ + // then the pre-image proof... + check_preimage("/hash") +} +``` + +Here, there are three `check_*` function calls. To enforce precedence in the +checks, every time a `check_*` function is executed and it fails the check +counter is incremented in the WACC VM. If the lock script succeeds, the check +counter value is pushed onto the stack as the payload of the +`SUCCESS(check_counter)` marker. + +Because logical operations in Rust short circuit, in the example lock script +above, the only time the `check_preimage` function will execute is if the +`check_signature("/keys/primary", "/entry/")` fails. The only time the +`check_signature("/keys/primary", "/entry/")` function will execute is if the +`check_signature("/keys/recovery", "/entry/")` function fails. If either +`check_signature` functions fail, their only side effect is to increment the +check counter. Then if the `check_preimage` succeeds, the marker left on the +stack will be `SUCCESS(2)`. If the `check_signature("/keys/primary", "/entry/")` +succeeds, the marker left on the stack will be `SUCCESS(1)`. If the +`check_signature("/keys/recovery", "/entry/")` succeeds, the marker left on the +stack will be `SUCCESS(0)`. If there are two competing entries and one provides +a valid signature and the other provides a valid preimage, the one with the +valid signature takes precedence over the one with the valid preimage because +the check counter is lower for the entry with the valid signature. + +Recovery key signatures (check_count=0) take precedence over primary key +signatures (check_count=1), which in turn take precedence over preimage proofs +(check_count=2). This hierarchy enables forced recovery: if a primary key is +compromised, the recovery key holder can create a competing entry that takes +precedence. + +In the case where competing entries satisfy the same lock script with the same +check count, the entry with the context key-path closest to the `"/"` branch +takes precedence. The solution for resolving a tie is for entry creators to +generate proof with a lower check count or proof that satisfies a lock script +with higher precedence. Single clause lock scripts are a bad idea for this +reason; they leave the door open for unresolvable ties in precedence. + +#### [Delegation](#delegation) + +The lock script mechanism is designed to support delegation. By adding lock +scripts associated with branches/leaves that can be satisfied by proofs +generated by 3rd parties, the owner of a log is delegating the management of +those branches/leaves to those people/services. The precedence and check +counter mechanism is designed to resolve any conflicts between competing +entries, giving a hierarchy of who can override whom when updating a log. + +In the example from above, let us assume we have the following: + +``` +─┬─ "/" + ╰─┬─ "keys/" + │ ├─── "primary" + │ ╰─── "recovery" + ├─ "hash" + ╰─┬─ "delegated/" + ├─┬─ "mike/" + │ ├─┬─ "keys/" + │ │ ╰─── "primary" + │ ╰─── "endpoint" + ╰─┬─ "walker/" + ├─┬─ "keys/" + │ ╰─── "primary" + ╰─── "peerid" + +``` + +And also assume the current entry has the following list of lock scripts +associated with each key-path: + +```json +[ + [ "/", "" ], + [ "/delegated/", ""], +] +``` + +Taking advantage of the `branch` function, we only need a single lock script to +govern all of the `"/delegated/"` branch: + +```rust +#[no_mangle] +fn lock() -> bool { + check_signature(branch("keys/primary"), "/entry/") +} +``` + +If Mike wished to update the value associated with the +`"/delegated/mike/endpoint"`, he would create a new Entry with a single `op`: + +```json +"ops": [ + { "update": [ "/delegated/mike/endpoint", { "str": [ "https://cryptid.tech" ] } ] }, +] +``` + +When validating his proposed entry for my log, first the `"/"` lock script +would run with the branch context of `"/"` and it would fail because Mike does +not possess the capability of creating proof (e.g. a digital signature) over +his entry that validates with the `"/"` lock script. After that fails, the lock +script for the `"/delegated/"` branch executes with the `"/delegated/mike/"` +branch context. If Mike's proposed entry is digitally signed with the secret +key that is associated with the public key value stored under +`"/delegated/mike/keys/primary"` then the entry validates and is accepted. + +If Walker wanted to update the value associated with +`"/delegated/walker/peerid"` then he would also create a new proposed event and +digitally sign it with the key pair associated with the public key stored under +`"/delegated/walker/keys/primary"` and the same `"/delegated/"` lock script works for +him because the context branch for validating his event is +`"/delegated/walker/"`. + +#### [Forced recovery using precedence](#forced-recovery-using-precedence) + +The purpose of the precedence is to create lock scripts with the hardest to +hack and most secure checks as the top precedences with less secure checks +having lower precedence. Today, the most secure kind of check is a threshold +signature generated through the cooperation of multiple independent +people/services using a quantum-resistant one-time signature schemes such as a +threshold Lamport signature. If the lock scripts have a threshold check that +takes precedence over a public key signature that in turn takes precedence over +a preimage check, then if a password (i.e. preimage) is compromised and the +attacker creates a new entry using a preimage proof, the rightful owner of the +log can then create a competing entry with the same sequence number using a +signature proof. The log "protocol" demands that the higher precedence entry +to be chosen as the next valid entry and not the one created by the attacker +who guessed your password. + +Most importantly, if a public key pair is compromised and an attacker attempts +a takeover by creating an entry using a signature proof, the rightful owner can +then contact their friends—or a threshold recovery service—and have them create +a threshold signature over a new entry that takes precedence over the +attacker's entry. The new entry with the threshold signature can then contain a +key rotation and key revocation mutation for the virtual key-value store +marking the compromised key as revoked as well as establishing a new valid +public keypair all without breaking the chain of trust in the log. + +You can think of this recovery scenario as a digital "social recovery" where +your friends collaborate to generate the threshold signature to recover your +control over your log. This can be applied in a number of real-world scenarios +such as board control over a corporation's identity log, heirs collaborating +with the deceased's counsel to take over the deceased's logs, or even parents +co-signing with their minor children to update the children's log for some +reason. + +One especially useful application is empowering maintainers of a Git repository +with full identity and access management (IAM) capabilities. By requiring that +the `"/"` branch lock script have a `check_signature("/maintainers")` threshold +signature check as the highest precedence, then the threshold group associated +with the "maintainers" threshold public key has full control over every +key-value pair in the provenance log. Maintainers can force rotate to a new +signing key for a contributor to recover to a known-good key. They can also +force rotate a contributor's signing key to NULL and remove their ability to +contribute to the repository. They can delegate branches and leaves in +contributors' provenance logs to enable a repository-wide service to update the +contributors' logs as needed. In short, a design like this gives the +maintainers of the repository full control over the IAM for contributors to the +repository all without a centralized server/service such as Github or Gitlab. + +## [Forking Provenance Logs](#forking-provenance-logs) + +It is possible to fork provenance logs by into any number of child logs. Child +logs always start with an entry that has a sequence number of `0` and a prev +CID that points at the entry in the parent log from which the child log is +forked. The lock script in the parent log entry is used as the lock script to +validate the first entry in the child log. By convention, along with the prev +CID pointing to the parent, the first entry in the child log must also contain +a mutation `op` that updates the VLAD of the parent to the `"/parent/"` +key-path. The parent VLAD is useful for when the parent log moves from one +content addressable storage to another. As long as a VLAD to CID mapping record +exists somewhere, then the CID can be resolved to the correct entry in the +parent log. + +### [Example Forking Using Delegation](#example-forking-using-delegation) + +Typically, the parent log maintains information about its child forks under the +`"/forks/"` branch. Using the delegation and branch mechanisms, it is trivial +to manage forking using a separate lock script assigned to the `"/forks/"` +key-path: + +```rust +#[no_mangle] +pub fn move_every_zig() -> bool { + // forking the parent be done by whomever can sign with "/forks/keys/primary" + check_signature(branch("keys/primary"), "/entry/") || + + // check the validity of the first entry of the child log + (check_eq(branch("vlad")) && check_signature(branch("keys/primary"), "/entry/")) +} +``` + +The process of forking a provenance log takes two steps. The first step is +recording the information about the child log in the parent log and the +second step is publishing the child log. By convention, each child log's +information is stored under its own branch under `"/forks/"`. The child's +branch name is arbitrary but, again by convention, under its branch there is a +`"vlad"` leaf with the child's VLAD, a `"keys/primary"` leaf with the child's +first advertised public key that was also used to sign its first entry and the +CID in its VLAD. + +When creating a child log fork, we create a new entry in the parent log with +the following mutation `op` values: + +```json +"ops": [ + { "noop": [ "/forks/" ] }, + { "update": [ "/forks/child1/vlad", { "bin": [ ] } ] }, + { "update": [ "/forks/child1/keys/primary", { "bin": [ ] } ] }, +] +``` + +This new entry is signed using the key pair associated with +`"/forks/keys/primary"`. The `noop` mutation is used to set the context +key-path to `"/forks/"` by including it the `op` mutation set of key-paths. It +is the longest common key-path and thus becomes the context key-path for the +`branch` function when running it to validate the new event in the parent log. + +When the lock script associated with `"/forks/"` executes, the first +`check_signature` call passes and the lock script exits with `SUCCESS(0)` on +the return stack. + +Then the first entry of the child log is created with the child VLAD as its +VLAD, the prev CID set to the CID of the parent event we just created. It must +include an `op` mutation to record the parent VLAD in the +`"/forks/child1/parent"` leaf of the child's key-value store: + +```json +"ops": [ + { "update": [ "/forks/child1/parent", { "bin": [ ] } ] }, + { "update": [ "/forks/child1/keys/primary", { "bin": [ ] } ] }, +] +``` + +The additional `"/forks/child1/keys/primary"` in the child log records the +signing key in the child in such a way that it doesn't disrupt the delegation +and forking lock script execution but allows for the child log to have its own +lock scripts and validate the next entry in the log. The only rule is that the +child's initial entry can only have `op` mutations under the `"/forks/child1/"` +key-path to ensure that the lock script from the parent executes correctly and +validates the child's first entry. + +The unlock script of the first entry in the child log must be like the +following: + +```rust +#[no_mangle] +pub fn unlock() { + // push the signature created using the /forks/child1/keys/primary keypair + push("/proofs/primary"); + + // push the vlad + push("/entry/vlad"); +} +``` + +When validating the first entry in the first child log, the unlock script +pushes the digital signature and the log's VLAD value onto the parameter stack. +This puts the parameter stack into the following state: + +``` + ┌──────────────────────┐ +top → │ <"/entry/vlad"> │ ← child vlad + ├──────────────────────┤ + │ <"/proofs/primary"> │ ← digital signature + ├──────────────────────┤ + │ ┆ │ + ┆ ┆ +``` + +The first entry of the child log must be signed using the key pair associated +with the pubkey recorded in the parent log under the +`"/forks/child1/keys/primary"` leaf. When the first entry is validated, the +lock script from the parent associated with the parent's `"/forks/"` key-path +will execute because the first entry in the child log only mutates key-paths +under `"/forks/"`. The context path this time will be `"/forks/child1/"` since +that is the longest common branch key-path in the `op` mutation set in the +child's first entry. + +When the parent log entry's lock script executes, the following steps are +executed: + +1. The `check_signature(branch("keys/primary"), "/entry/")` fails because it + sees a VLAD on top of the stack and not a signature. +2. The `check_eq(branch("vlad"))` succeeds because the `<"/forks/child1/vlad">` + value in the parent matches the "VLAD" value pushed on the stack by the + child unlock script. It pops the VLAD parameter off of the parameter stack. +3. The `check_signature(branch("keys/primary"), "/entry/")` succeeds because the + `<"/forks/child1/keys/primary">` public key in the parent validates the + signature `<"/proofs/primary">` on the stack using the message from the + proposed state at `"/entry/"`. It pops the signature off of the parameter + stack. +4. A `SUCCESS(1)` marker is pushed onto the results stack because the initial + `check_signature` function failed, incrementing the check counter once. + +The initial entry in the child log should also set up it's own lock scripts +for governing its key-value store. The data recorded under the +`"/forks/child1/"` branch in the child's key-value store can be used with lock +scripts in the child to validate the second entry in the child provenance log +which can then remove the `"/forks/child1/"` branch if so desired. + +[0]: https://cryptid.tech +[1]: https://github.com/cryptidtech/provenance-specifications/ +[2]: https://github.com/multiformats/multiformats +[3]: https://github.com/CommunitySpecification/1.0 +[4]: https://github.com/bitcoin/bips/blob/master/bip-0342.mediawiki +[5]: https://github.com/cryptidtech/provenance-specifications/blob/main/specifications/nonce.md +[6]: https://github.com/cryptidtech/provenance-specifications/blob/main/specifications/multisig.md +[7]: https://github.com/cryptidtech/provenance-specifications/blob/main/specifications/multikey.md +[8]: https://github.com/cryptidtech/provenance-specifications/blob/main/specifications/wacc.md +[9]: https://github.com/cryptidtech/provenance-specifications/blob/main/specifications/vlad.md +[10]: https://github.com/cryptidtech/multihash.git + +[LIPMAA]: https://github.com/AljoschaMeyer/bamboo/blob/master/README.md#links-and-entry-verification diff --git a/specifications/wacc-api.md b/specifications/wacc-api.md index 7fceb91..f358e8b 100644 --- a/specifications/wacc-api.md +++ b/specifications/wacc-api.md @@ -64,27 +64,7 @@ All WACC API functions are imported from the "wacc" module and prefixed with und (i32.const 7)) ;; length of "/pubkey" ``` -### 2. _pop - -**Signature:** -```wasm -(import "wacc" "_pop" (func $pop (result i32))) -``` - -**Parameters:** None - -**Returns:** -- i32: 1 on success, 0 on failure - -**Operation:** -1. Removes top value from parameter stack -2. Discards the value -3. Returns success/failure - -**Errors:** -- Empty parameter stack - -### 3. _check_eq +### 2. _check_eq **Signature:** ```wasm @@ -112,7 +92,7 @@ All WACC API functions are imported from the "wacc" module and prefixed with und - Type mismatch - Maximum check count exceeded -### 4. _check_preimage +### 3. _check_preimage **Signature:** ```wasm @@ -148,7 +128,7 @@ All WACC API functions are imported from the "wacc" module and prefixed with und - Only whitelisted hash algorithms are accepted - Weak algorithms (MD5, SHA-1) are rejected -### 5. _check_signature +### 4. _check_signature **Signature:** ```wasm @@ -187,7 +167,7 @@ All WACC API functions are imported from the "wacc" module and prefixed with und - Only whitelisted signature algorithms accepted - Supports Ed25519, secp256k1, P-256, BLS12-381 -### 6. _branch +### 5. _branch **Signature:** ```wasm @@ -216,7 +196,7 @@ All WACC API functions are imported from the "wacc" module and prefixed with und **Usage:** Used to create hierarchical key paths like `/root/child/key`. -### 7. _log +### 6. _log **Signature:** ```wasm @@ -242,7 +222,7 @@ Used to create hierarchical key paths like `/root/child/key`. **Usage:** Debug and audit logging from WASM scripts. -### 8. _push_value +### 7. _push_value **Signature:** ```wasm @@ -276,7 +256,7 @@ Used by unlock scripts to push raw byte data (such as a public key or preimage) (i32.const 32)) ;; length of bytes ``` -### 9. _check_preimage_value +### 8. _check_preimage_value **Signature:** ```wasm @@ -372,7 +352,7 @@ Values on stacks can be: ### Algorithm Whitelisting **Allowed Hash Algorithms:** -- SHA-2: sha2-256 (0x12), sha2-512 (0x13), sha2-384 (0x20), sha2-224 (0x1013) +- SHA-2: sha2-256 (0x12), sha2-512 (0x13), sha2-384 (0x20) - SHA-3: sha3-256 (0x16), sha3-512 (0x14), sha3-384 (0x15), sha3-224 (0x17) - Keccak: keccak-256 (0x1b), keccak-512 (0x1d), keccak-384 (0x1c), keccak-224 (0x1a) - Blake: blake3 (0x1e), blake2b-256 (0xb220), blake2b-512 (0xb240), blake2s-256 (0xb260) @@ -514,7 +494,7 @@ Multiple instances can run in parallel, each with: ## Conformance A conforming WACC VM implementation MUST: -1. Implement all 9 API functions with specified signatures +1. Implement all 8 API functions with specified signatures 2. Enforce security limits (fuel, checks, memory, log) 3. Reject weak cryptographic algorithms 4. Validate all inputs (keys, paths, memory operations)