Repository navigation
Description
Summary
Flow CLI's google-kms signer produces signatures that fail on-chain verification when the account key is registered with hashAlgorithm: SHA3_256, even when the Flow CLI account config in flow.json
declares "hashAlgorithm": "SHA3_256". The same signer, same GCP KMS key, same transaction template works correctly when the on-chain key is registered with SHA2_256.
Root cause appears to be that the google-kms signer always hashes the transaction envelope with SHA2_256 (because the underlying GCP KMS asymmetric-sign API ties the algorithm name EC_SIGN_P256_SHA256
to a SHA256 digest in the request shape), regardless of the hashAlgorithm field in the flow.json account config. When the on-chain account key is registered with SHA3_256, Flow's verifier hashes the
envelope with SHA3_256, which doesn't match the SHA2_256 digest the signer actually signed → signature is not valid.
Reproduction on mainnet (verifiable on the block explorer)
Two mainnet accounts share the same P-256 pubkey (backed by the same GCP KMS P-256 key, algorithm EC_SIGN_P256_SHA256) registered with different hashAlgorithm values:
- Account A —
0xad2b341394a2e19a— Key 0: ECDSA_P256, SHA3_256, weight 1000, pubkey755a4234...314a5d7a44ce - Account B —
0x6dec6e64a13b881e— Key 0: ECDSA_P256, SHA2_256, weight 1000, pubkey (same as A)755a4234...314a5d7a44ce
Both accounts' key layouts are observable via flow accounts get --include keys --network mainnet.
flow.json for the reproduction differs only in hashAlgorithm:
{
"accounts": {
"acct-a": {
"address": "0xad2b341394a2e19a",
"key": { "type": "google-kms", "index": 0, "signatureAlgorithm": "ECDSA_P256",
"hashAlgorithm": "SHA3_256", "resourceID": "projects/.../cryptoKeyVersions/1" }
},
"acct-b": {
"address": "0x6dec6e64a13b881e",
"key": { "type": "google-kms", "index": 0, "signatureAlgorithm": "ECDSA_P256",
"hashAlgorithm": "SHA2_256", "resourceID": "projects/.../cryptoKeyVersions/1" }
}
}
}
Account B (SHA2_256) — WORKS
Tx 0x07a0c641e4ccac13165ae6a0168be819f712ea650c8c5e1506d8b9c75633c488 sealed cleanly, flow.AccountKeyAdded emitted, verified on-chain.
https://www.flowscan.io/tx/07a0c641e4ccac13165ae6a0168be819f712ea650c8c5e1506d8b9c75633c488
Account A (SHA3_256) — FAILS
Tx 0x3e687ff5b46f729d4f622657fa719310927249e0bdefd532b72785ae99ac9369 sealed but failed verification:
Status SEALED
Transaction Error:
[Error Code: 1009] ... transaction verification failed:
[Error Code: 1006] invalid proposal key: public key 0 on account ad2b341394a2e19a does not have a valid signature:
[Error Code: 1009] invalid envelope key: public key 0 on account ad2b341394a2e19a does not have a valid signature:
signature is not valid
https://www.flowscan.io/tx/3e687ff5b46f729d4f622657fa719310927249e0bdefd532b72785ae99ac9369
Emulator reproduction without mainnet access: create a test account carrying a P-256 / SHA3_256 key backed by a mocked GCP KMS stub. Same behavior should reproduce 1-for-1.
Expected behavior
For flow transactions send ... with "type": "google-kms" and "hashAlgorithm": "SHA3_256":
- Flow CLI builds the tx envelope.
- Flow CLI computes the SHA3_256 digest of the envelope (not SHA2_256).
- Flow CLI calls GCP KMS
AsymmetricSignwith that 32-byte digest. - GCP KMS signs the bytes it was given — it does not re-hash. See GCP asymmetric signing algorithms doc — client is
responsible for hashing. The_SHA256inEC_SIGN_P256_SHA256is a request-shape type marker, not a server-side step. - Flow CLI attaches the signature; Flow verifies the envelope using SHA3_256 (per the on-chain registration), matches; passes.
Actual behavior
Step 2 appears hardcoded to SHA2_256 regardless of flow.json's hashAlgorithm, producing a signature over the wrong digest. Flow verification (step 5, which correctly uses SHA3_256 per the on-chain
registration) rejects.
Why there's no user-side workaround
The hashAlgorithm in flow.json must match the on-chain registration (Flow's verifier uses the on-chain value, not the submitter's claim). Lying about it in flow.json still fails. The signer has to
compute the correct digest.
Likely fix location
google-kms signer in Flow CLI resolves flow.json's signer type and delegates to the GCP KMS integration in flow-go-sdk's crypto/cloudkms package (or thereabouts).
Pseudo-fix:
// Current (likely): hasher pinned to SHA2_256 because the KMS algorithm name contains "SHA256".
digest := sha256.Sum256(envelope.Bytes())
sig, err := kmsClient.AsymmetricSign(ctx, keyName, digest[:])
// Fix: honor the hashAlgorithm declared on the account key.
hasher, err := crypto.NewHasher(accountKey.HashAlgorithm) // SHA2_256 or SHA3_256
if err != nil { return nil, err }
digest := hasher.ComputeHash(envelope.Bytes())
sig, err := kmsClient.AsymmetricSign(ctx, keyName, digest)
GCP's AsymmetricSignRequest has a Digest oneof with sha256/sha384/sha512. For an EC_SIGN_P256_SHA256 key, only sha256 is accepted at request-shape — but the 32 bytes can be any digest. GCP does
not re-hash.
If there's an upfront validation rejecting SHA3_256 for EC_SIGN_P256_SHA256 keys, it should be lifted: the on-chain registration is the source of truth, not the KMS algorithm name.
Suggested regression test
Emulator integration test:
- Create an emulator account with P-256 pubkey registered
hashAlgorithm: SHA3_256, weight 1000. - Config
google-kmssigner with a backing stub that returns a real ECDSA signature over whatever digest it receives. - Sign a no-op transaction (
transaction { prepare(signer: &Account) {} }); submit; assertSEALEDwithouttransaction verification failed. - Sibling account with
hashAlgorithm: SHA2_256as control — both should pass.
Impact
Any Cadence account with a P-256 key registered as SHA3_256, backed by GCP KMS, cannot be signed for by Flow CLI's google-kms signer. The sibling secp256k1 / SHA2_256 case works. aws-kms signers not
affected (round-trip tested). Bug is specific to the P-256 + SHA3_256 corner of google-kms signing.
Priority
Medium — correctness bug in a corner of signer support, blocks a specific use case completely with no user-side workaround.
Activity
vishalchangrani commentedon Nov 7, 2026
Workaround confirmed working end-to-end
Wrote a small Python-side workaround that bypasses the
google-kmssigner and submits a SHA3_256-signed transaction directly. It confirms the diagnosis and gives a working path until the fix ships:flow_py_sdk.(domain_tag || envelope_bytes)with SHA3_256 locally.KeyManagementServiceClient.asymmetric_signon the GCP KMS key, passing the 32-byte SHA3_256 digest in the request'sdigest.sha256field — GCP KMS does not re-hash; it signs the 32 bytesas-is. The
_SHA256inEC_SIGN_P256_SHA256is a request-shape type marker.r || s.Mainnet verification: tx
0xaef3e3fefcef7619567195e0420970f9b7b30f412237e708b9af3584e5e42d5asealed cleanly andverified on the SHA3_256 account from the original issue (
0xad2b341394a2e19a). Immediately before that, the same tx template through the standardflow transactions sendpath with thegoogle-kmssignerfailed with the same
signature is not validerror from the issue body — confirming SHA2_256 is the only thing being hashed.Core custom-signer logic (Python)