Skip to content

feat: add VerifyDER, test vector generator, and snap planning docs#176

Open
salindne wants to merge 4 commits intomainfrom
feat/phase1-crypto-compat
Open

feat: add VerifyDER, test vector generator, and snap planning docs#176
salindne wants to merge 4 commits intomainfrom
feat/phase1-crypto-compat

Conversation

@salindne
Copy link
Copy Markdown
Contributor

@salindne salindne commented Apr 1, 2026

Summary

Phase 1 of the MetaMask Snap non-custodial signing effort (see docs/non-custodial-snap-plan.md):

  • VerifyDER (pkg/keys/canton_keys.go) — Server-side verification of DER-encoded ECDSA signatures against a compressed public key and pre-hashed digest. Validates DER structure, low-S normalization, and signature correctness. Needed by the transfer execute endpoint to validate client-provided signatures before forwarding to Canton.
  • cmd/generate-test-vectors — Generates deterministic crypto test vectors (SPKI DER, fingerprints, DER signatures) from hardcoded private keys. Output is a JSON file for cross-validation with the canton-snap TypeScript implementation. Run via make test-vectors.
  • Planning docsdocs/non-custodial-snap-plan.md (full hybrid architecture plan) and docs/phase1-crypto-compatibility.md (detailed Phase 1 task breakdown).

Test plan

  • TestVerifyDER — valid sig, wrong hash, wrong pubkey, malformed DER, wrong hash length, trailing bytes
  • TestVerifyDERWithKnownVector — deterministic key produces verifiable signature
  • cmd/generate-test-vectors self-verifies all generated signatures via VerifyDER
  • make lint passes
  • go test ./pkg/keys/ passes

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive implementation plan for non-custodial signing via MetaMask Snaps, alongside a new utility for generating deterministic crypto test vectors to ensure cross-platform compatibility with TypeScript. The changes also include the addition of a VerifyDER function in the keys package to validate DER-encoded ECDSA signatures. Review feedback suggests improving the VerifyDER implementation by using standard library constants for the secp256k1 curve order and adopting more efficient, idiomatic signature verification methods instead of public key recovery.

}

// Verify low-S normalization (BIP-62): s must be <= n/2
secp256k1N, _ := new(big.Int).SetString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Instead of hardcoding the secp256k1 curve order as a string, use the constant provided by the crypto package. This improves maintainability and reduces the risk of typos.

Suggested change
secp256k1N, _ := new(big.Int).SetString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", 16)
secp256k1N := crypto.S256().Params().N

Comment on lines +326 to +341
// Try recovery IDs 0 and 1 to find which one recovers the correct public key
for v := byte(0); v <= 1; v++ {
recoverSig := make([]byte, 65)
copy(recoverSig, rawSig)
recoverSig[64] = v

recoveredPub, recErr := crypto.SigToPub(hash, recoverSig)
if recErr != nil {
continue
}
if crypto.PubkeyToAddress(*recoveredPub) == crypto.PubkeyToAddress(*pubKey) {
return nil
}
}

return fmt.Errorf("signature verification failed")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a recovery loop (SigToPub) to verify a signature when the public key is already known is inefficient. It is more idiomatic and performant to use crypto.VerifySignature directly. Additionally, comparing public keys directly is more robust than comparing their Ethereum addresses, as it avoids the overhead of hashing and the theoretical (though negligible) risk of collisions.

	// Verify the signature against the public key
	if !crypto.VerifySignature(crypto.FromECDSAPub(pubKey), hash, rawSig) {
		return fmt.Errorf("signature verification failed")
	}

	return nil

Add VerifyDER to pkg/keys for server-side validation of client-provided
DER signatures before forwarding to Canton. This is needed for the
non-custodial transfer execute endpoint.

Add cmd/generate-test-vectors to produce deterministic crypto test
vectors (SPKI DER, fingerprints, DER signatures) for cross-validation
with the canton-snap TypeScript implementation.

Add planning docs for the MetaMask Snap non-custodial signing approach.
@salindne salindne force-pushed the feat/phase1-crypto-compat branch from 3b19432 to d2060f8 Compare April 1, 2026 18:32
salindne added 3 commits April 1, 2026 12:34
Integration test that proves Canton accepts signatures from
test vector keys — the same keys validated by canton-snap
TypeScript cross-validation tests (T3).

Test flow: register external party with test vector key → mint
→ prepare transfer → sign with test vector key → execute → verify
balances.

Run with: go run scripts/testing/test-snap-crypto.go
Requires local Canton environment (bootstrap-local.sh).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant