Draft
The appearance of new networks in which a user can reuse the same key for the same contracts carries new risks. The proposed solution allows using same keys and contracts across different L2 networks without worrying about new replay attacks, as the signatures themselves will be different everywhere and will depend on a unique network ID (later referred to as global_id).
Most contracts verify the signature of external message with CHKSIGNU or CHKSIGNS. Therefore, to minimize the number of any changes, we propose adding new behavior for these opcodes (enabled/disabled via capability). Fow now, this new behavior can only be implemented on the L2 side, however, it requires at least the reservation of three additional opcodes, which are needed for explicit verification of signatures with other ids.
VM state now includes an additional stack for signature domains. Its current top item is referred to as "the current signature domain" and is used as a behaviour modifier for signature verification opcodes (CHKSIGNU and CHKSIGNS for now). For L2 networks their signature domain is implicitly added to the stack before contract execution, while in the main networks execution starts with an empty stack. An empty stack is equivalent to any stack with no_signature_domain item on top.
// Non-empty variant. A root hash of its Cell representation
// is used as a prefix for the verified data.
signature_domain#2d72df7d global_id:int32 = SignatureDomain;
// Special variant to NOT add any prefix for the verified data.
// Can be used to verify mainnet signatures from L2 networks.
no_signature_domain#b65d972d = SignatureDomain;-
f910CHKSIGNUandf911 CHKSIGNS- whenSignatureWithIdcapability is enabled the resulting data for verification is prefixed with a byte slice computed from the current signature domain. Forsignature_domainvariant this is a 32-byte slice with a root hash of the cell representation. Forno_signature_domainthis is an empty slice (so the verification result is identical to the original implementation).Example for
CHKSIGNU:global_id = 1000 # 0x000003e8 prefix = CellBuilder().store_u32(0x2d72df7d).store_i32(global_id).build().repr_hash # prefix = bytes.fromhex("9f78986f23148053358938611edcbcfdcd1e7844c5522a87ccefcd7ff55bc4e5") data = 0xdeadbeef signature = ... public_key = ... data_to_check = prefix + data.to_bytes(32, byteorder='big') # data_to_check = [0x9f, 0x78, ..., 0xc4, 0xe5, 0x00, 0x00, 0x00, .., 0xbe, 0xef] ed25519_verify(data_to_check, signature, public_key)
Example for
CHKSIGNS:global_id = 1000 # 0x000003e8 prefix = CellBuilder().store_u32(0x2d72df7d).store_i32(global_id).build().repr_hash # prefix = bytes.fromhex("9f78986f23148053358938611edcbcfdcd1e7844c5522a87ccefcd7ff55bc4e5") slice = CellSlice("deadbeef") signature = ... public_key = ... data_hash = sha256(slice.data) # data_hash = [0x5f, 0x78, 0xc3, ..] data_to_check = prefix + data_hash # data_to_check = [0x9f, 0x78, ..., 0xc4, 0xe5, 0x5f, 0x78, 0xc3, ..] ed25519_verify(data_to_check, signature, public_key)
f91800SIGNDOMAIN(- x or ⊥) - pushes the current signature domain wherexis aglobal_idor Null if there were no signature domain.f91801SIGNDOMAIN_POP(- x or ⊥) - same asSIGNDOMAIN, but removes the fetched value from the signature domain stack.f91802SIGNDOMAIN_PUSH(x or ⊥ -) - pushes an optionalglobal_idto the signature domain stack making it the current signature domain.-2^31 ≤ x < 2^31
- L2 side - must support these opcodes at the VM level;
Example of the implemented changes in Tycho VM.
- Wallets and libraries - must have a context with a known
global_idof the selected network when working with signatures;Example of the usage of such context with JS SDK.
- Ledger - must use the provided signature id (if any) when forming a signed external message;
-
[x] Do behavior need to be changed for other opcodes (P256_CHKSIGNU,BLS_VERIFYor others)?Resolved: there is no need to support this behavior for anything other than basic signatures. Alternative cryptography is mostly used in specialized cases where no one will just deploy the same contracts across multiple networks as is.
NOTE: Signature verification for algorithms other than Ed25519 is yet to be implemented in
tycho-vm -
Should we really compute a prefix as a cell hash?
Pros:
- Can potentially store byte-unaligned data.
Cons:
- Adds a strange dependency on cells for crypto modules in SDK's.
- Increases the amount of data being checked (4 bytes vs 32 bytes).
-
Should signature domain contain workchain or address?
Pros:
- Adds an additional guard against replaying messages.
Cons:
- It's unclear how to verify signatures that are passed between workchains or those not tied to any address (e.g. election data signatures). Could potentially make contracts much more complicated since you'd need to know some variable context everywhere.
- If for some reason some contract needs to be deployed with signature in its StateInit data, it won't be possible to compute its address and keep the signature valid at the same time.