diff --git a/README.md b/README.md index 323e56a..032b6e9 100644 --- a/README.md +++ b/README.md @@ -490,7 +490,7 @@ validators: # validator nodes specification The `spin-node.sh` triggers genesis generator (`generate-genesis.sh`) which generates the following files based on `validator-config.yaml`: 1. **post-quantum secure validator keypairs** in `genesis/hash-sig-keys` unless already generated or forced with `--forceKeyGen` -2. **config.yaml** - Updated genesis time, `ATTESTATION_COMMITTEE_COUNT`, and `GENESIS_VALIDATORS` with **attestation** and **proposal** public keys per validator (dual-key layout / `hash-sig-cli:devnet4`) +2. **config.yaml** - Updated genesis time, `ATTESTATION_COMMITTEE_COUNT`, and `GENESIS_VALIDATORS` with **attestation** and **proposal** public keys per validator (dual-key layout, 32-byte SSZ pubkeys / `ghcr.io/lambdaclass/hash-sig-cli:0.5.0`) 3. **validators.yaml** - Validator index assignments using round-robin distribution 4. **nodes.yaml** - ENR (Ethereum Node Records) for peer discovery 5. **genesis.json** - Genesis state in JSON format @@ -512,7 +512,7 @@ You can also run the generator standalone: #### Hash-Based Signature (Post-Quantum) Scheme Validator Keys -**Tool's Docker Image**: `HASH_SIG_CLI_IMAGE="blockblaz/hash-sig-cli:devnet4"` +**Tool's Docker Image**: `HASH_SIG_CLI_IMAGE="ghcr.io/lambdaclass/hash-sig-cli:0.5.0"` **Source**: https://github.com/blockblaz/hash-sig-cli Using the above docker tool the following files are generated (unless already generated or forced via `--forceKeyGen` flag): @@ -710,9 +710,10 @@ The manifest file (`validator-keys-manifest.yaml`) contains metadata about all g # Hash-Signature Validator Keys Manifest # Generated by hash-sig-cli -key_scheme: SIGTopLevelTargetSumLifetime32Dim64Base8 +key_scheme: XmssTargetSumLifetime32Dim42Base8 hash_function: Poseidon2 encoding: TargetSum +pubkey_bytes: 32 lifetime: 4294967296 log_num_active_epochs: 10 num_active_epochs: 1024 @@ -732,6 +733,10 @@ validators: proposer_key_privkey_file: validator_1_proposer_key_sk.ssz ``` +`key_scheme` and `pubkey_bytes` are derived from the constants of the XMSS revision the image was +built against, so they report the actual format on disk instead of a fixed label. `generate-genesis.sh` +reads `pubkey_bytes` back to detect a key directory left over from an older image. + (See [hash-sig-cli](https://github.com/blockblaz/hash-sig-cli) for the exact manifest schema.) ## Troubleshooting diff --git a/ansible/roles/ethlambda/defaults/main.yml b/ansible/roles/ethlambda/defaults/main.yml index 87206bd..db91204 100644 --- a/ansible/roles/ethlambda/defaults/main.yml +++ b/ansible/roles/ethlambda/defaults/main.yml @@ -3,5 +3,5 @@ # Note: These are fallback defaults. Actual values are extracted from client-cmds/ethlambda-cmd.sh # in the tasks/main.yml file. These defaults are used if extraction fails. -ethlambda_docker_image: "ghcr.io/lambdaclass/ethlambda:devnet4" +ethlambda_docker_image: "ghcr.io/lambdaclass/ethlambda:devnet5-leanvm-main" deployment_mode: docker # docker or binary diff --git a/ansible/roles/ethlambda/tasks/main.yml b/ansible/roles/ethlambda/tasks/main.yml index 28e222d..e474d3a 100644 --- a/ansible/roles/ethlambda/tasks/main.yml +++ b/ansible/roles/ethlambda/tasks/main.yml @@ -26,7 +26,7 @@ - name: Set docker image and deployment mode from client-cmd.sh set_fact: - ethlambda_docker_image: "{{ ethlambda_docker_image_raw.stdout | trim | default('ghcr.io/lambdaclass/ethlambda:devnet4') }}" + ethlambda_docker_image: "{{ ethlambda_docker_image_raw.stdout | trim | default('ghcr.io/lambdaclass/ethlambda:devnet5-leanvm-main') }}" deployment_mode: "{{ ethlambda_deployment_mode_raw.stdout | trim | default('docker') }}" - name: Extract node configuration from validator-config.yaml diff --git a/client-cmds/ethlambda-cmd.sh b/client-cmds/ethlambda-cmd.sh index 1ea79ee..4b73790 100644 --- a/client-cmds/ethlambda-cmd.sh +++ b/client-cmds/ethlambda-cmd.sh @@ -51,7 +51,7 @@ node_binary="$binary_path \ $checkpoint_sync_flag" # Command when running as docker container -node_docker="ghcr.io/lambdaclass/ethlambda:devnet4 \ +node_docker="ghcr.io/lambdaclass/ethlambda:devnet5-leanvm-main \ --genesis /config/config.yaml \ --validators /config/annotated_validators.yaml \ --bootnodes /config/nodes.yaml \ diff --git a/generate-genesis.sh b/generate-genesis.sh index 85a31f9..b27be3d 100755 --- a/generate-genesis.sh +++ b/generate-genesis.sh @@ -168,7 +168,7 @@ fi echo " ✅ docker found: $(which docker)" # Hash-sig-cli Docker image (separate attester + proposer keys per validator when using dual-key manifest) -HASH_SIG_CLI_IMAGE="blockblaz/hash-sig-cli:latest" +HASH_SIG_CLI_IMAGE="ghcr.io/lambdaclass/hash-sig-cli:0.5.0" echo " ✅ Using hash-sig-cli Docker image: $HASH_SIG_CLI_IMAGE" echo "" @@ -263,14 +263,13 @@ if [ "$SHOULD_SKIP" == "true" ]; then echo "" else echo " Generating keys for $VALIDATOR_COUNT validators..." - echo " Using scheme: SIGTopLevelTargetSumLifetime32Dim64Base8" echo " Key directory: $HASH_SIG_KEYS_DIR" echo "" - # Generate hash-sig keys for all validators using Docker - # Scheme: SIGTopLevelTargetSumLifetime32Dim64Base8 + # Generate hash-sig keys for all validators using Docker. + # The signature scheme is whatever the pinned image was built against; it is + # reported back in validator-keys-manifest.yaml rather than assumed here. # Active epochs: 2^ACTIVE_EPOCH (from validator-config.yaml) - # Total lifetime: 2^32 (4,294,967,296) # Convert to absolute path for Docker volume mounting GENESIS_DIR_ABS="$(cd "$GENESIS_DIR" && pwd)" @@ -347,14 +346,41 @@ if [ "$DUAL_KEY_MODE" = true ]; then echo " ❌ Error: Could not read attester/proposer pubkeys from manifest" exit 1 fi + # Whole bytes only, so the byte count derived below is exact rather than rounded. for pk in "$ATTEST_PUB" "$PROP_PUB"; do - if [[ ! "$pk" =~ ^0x[0-9a-fA-F]+$ ]]; then + if [[ ! "$pk" =~ ^0x([0-9a-fA-F]{2})+$ ]]; then echo " ❌ Error: Manifest does not contain hex pubkeys (dual-key mode)" echo " Found: $pk" exit 1 fi done echo " ✅ Manifest verified - dual-key format (attester + proposer)" + + # Cross-check the pubkey length the manifest declares against the pubkeys it holds. + # Key file names did not change when the format moved to leanVM's XMSS, so a key + # directory left over from an older image passes the "keys already exist" check + # above and would otherwise be baked into config.yaml unnoticed. + MANIFEST_KEY_SCHEME=$(yq eval '.key_scheme // ""' "$MANIFEST_FILE" 2>/dev/null) + MANIFEST_PUBKEY_BYTES=$(yq eval '.pubkey_bytes // ""' "$MANIFEST_FILE" 2>/dev/null) + ATTEST_PUB_BYTES=$(( (${#ATTEST_PUB} - 2) / 2 )) + echo " Key scheme: ${MANIFEST_KEY_SCHEME:-}" + echo " Public key size: ${ATTEST_PUB_BYTES} bytes" + if [ -z "$MANIFEST_PUBKEY_BYTES" ]; then + echo " ⚠️ Manifest reports no 'pubkey_bytes' - these keys predate hash-sig-cli 0.5.0" + echo " and are in the retired leanSig format. Clients built against leanVM's XMSS" + echo " cannot read them; the genesis below will not start such a devnet." + echo " Regenerate: ./generate-genesis.sh \"$GENESIS_DIR\" --forceKeyGen" + elif [[ ! "$MANIFEST_PUBKEY_BYTES" =~ ^[0-9]+$ ]]; then + # Guard the arithmetic comparison below: a non-numeric value makes `[ -ne ]` fail + # as a condition, which would skip the check instead of reporting anything. + echo " ❌ Error: manifest 'pubkey_bytes' is not a number: $MANIFEST_PUBKEY_BYTES" + echo " The manifest is inconsistent - regenerate it rather than editing it by hand" + exit 1 + elif [ "$ATTEST_PUB_BYTES" -ne "$MANIFEST_PUBKEY_BYTES" ]; then + echo " ❌ Error: manifest declares pubkey_bytes=$MANIFEST_PUBKEY_BYTES but its pubkeys are $ATTEST_PUB_BYTES bytes" + echo " The manifest is inconsistent - regenerate it rather than editing it by hand" + exit 1 + fi else FIRST_PUBKEY=$(yq eval ".validators[0].$PUBKEY_FIELD" "$MANIFEST_FILE" 2>/dev/null) if [ -z "$FIRST_PUBKEY" ]; then