-- GajumaruSerialization.asn -- -- ASN.1 model for the structures serialized by gmserialization.erl -- (the layer on top of RLP). -- -- Purpose: -- * Provide a formal, toolable description of the data. -- * Enable migration from the legacy RLP-based format to DER. -- -- Detection of legacy vs new: -- Legacy data (produced by gmserialization:serialize / gmser_chain_objects:serialize -- or gmser_dyn:serialize) is always an RLP *list* at the top level. -- This means the first byte is in the range 0xC0 .. 0xFF. -- -- DER-encoded data using the types below will start with 0x30 (SEQUENCE, -- constructed, universal tag) for the outermost GajumaruData (or any -- top-level SEQUENCE we define). 0x30 < 0xC0, so a single-byte prefix -- check reliably distinguishes the two formats. -- -- Usage in Erlang (after compiling with asn1ct): -- {ok, Mod} = asn1ct:compile(GajumaruSerialization.asn, [ber, der]). -- {ok, Term} = 'GajumaruSerialization':decode('GajumaruData', DerBinary). -- -- Notes on type mapping from gmserialization.erl: -- - int -> INTEGER (new data uses canonical DER INTEGER) -- - binary -> OCTET STRING -- - bool -> BOOLEAN -- - id -> Id (SEQUENCE) (cleaner than the legacy packed 33-byte form) -- - [T] -> SEQUENCE OF T -- - {T1,...} -> SEQUENCE (fixed-size, order matters) -- - #{items := [{K,T},...]} -> SEQUENCE with the fields in template order -- (static maps carry *values only*, no keys on the wire) -- - tag + vsn are preserved as the first two fields for compatibility -- with existing dispatch logic. -- -- Legacy integers had strict unsigned-minimal no-leading-zero (except for 0) -- and RLP-level rules. New DER data does not need to follow those rules. -- -- WARNING: -- Any cryptographic hash or signature computed over a serialized object -- will change when switching from RLP to DER for that object. Plan a -- coordinated upgrade. GajumaruSerialization DEFINITIONS AUTOMATIC TAGS ::= BEGIN EXPORTS ALL; -- ============================================================ -- Top-level wrapper used for new DER data. -- This is what a decoder will see first. -- ============================================================ GajumaruData ::= SEQUENCE { tag INTEGER, vsn INTEGER, content Content } -- Content can be a specific structured type (preferred) or a generic -- representation of a template-driven object. Content ::= CHOICE { -- Generic fallback that can represent any [{Field, Type}] template -- without having a pre-defined SEQUENCE for every object. templateFields [0] TemplateFields, -- Examples of concrete versioned types (extend as needed) account [1] Account, signedTx [2] SignedTx, contract [3] ContractCode -- Add more alternatives for other tags from gmser_chain_objects } -- ============================================================ -- Generic template-driven representation -- (useful during transition or for unregistered types) -- ============================================================ TemplateFields ::= SEQUENCE OF TemplateField TemplateField ::= SEQUENCE { -- Field name is included for debuggability / generic processing. -- In the original static encoding the name is NOT on the wire; -- only position and type matter. We include it here for convenience. name IA5String OPTIONAL, value Value } Value ::= CHOICE { intValue [0] INTEGER, boolValue [1] BOOLEAN, binaryValue [2] OCTET STRING, idValue [3] Id, listValue [4] SEQUENCE OF Value, tupleValue [5] SEQUENCE OF Value, mapValue [6] SEQUENCE OF KeyValue -- only needed if you want to -- represent dynamic-style maps } KeyValue ::= SEQUENCE { key Value, val Value } -- ============================================================ -- Common types -- ============================================================ Id ::= SEQUENCE { -- Corresponds to the simple tags in gmser_id (account=1, name=2, etc.) -- and the extended account subtype (high bit in legacy). type INTEGER (0..255), value OCTET STRING (SIZE (32)) } -- ============================================================ -- Concrete object examples (derived from usage in the codebase) -- Add / evolve per version as you introduce new vsns. -- ============================================================ -- Example: a very simple account-like object used in tests Account ::= SEQUENCE { foo INTEGER, bar OCTET STRING } -- Simplified signed transaction SignedTx ::= SEQUENCE { signatures SEQUENCE OF OCTET STRING, transaction OCTET STRING } -- Contract code objects (see gmser_contract_code.erl) -- We model the three versions that exist today. ContractCode ::= CHOICE { v1 [0] ContractV1, v2 [1] ContractV2, v3 [2] ContractV3 } ContractV1 ::= SEQUENCE { sourceHash OCTET STRING, -- typeInfo is a list of 4-tuples in legacy: -- {typeHash, name, argType, outType} typeInfo SEQUENCE OF TypeInfoV1, byteCode OCTET STRING } ContractV2 ::= SEQUENCE { sourceHash OCTET STRING, typeInfo SEQUENCE OF TypeInfoV1, byteCode OCTET STRING, compilerVersion OCTET STRING } ContractV3 ::= SEQUENCE { sourceHash OCTET STRING, typeInfo SEQUENCE OF TypeInfoV3, byteCode OCTET STRING, compilerVersion OCTET STRING, payable BOOLEAN } TypeInfoV1 ::= SEQUENCE { typeHash OCTET STRING, name OCTET STRING, argType OCTET STRING, outType OCTET STRING } TypeInfoV3 ::= SEQUENCE { typeHash OCTET STRING, name OCTET STRING, payable BOOLEAN, argType OCTET STRING, outType OCTET STRING } -- ============================================================ -- Notes for implementers -- ============================================================ -- 1. Detection (recommended decoder entry point): -- -- decode(Binary) -> -- case Binary of -- <> when B >= 16#C0 -> -- decode_legacy_rlp(Binary); % existing gmser_* path -- _ -> -- {ok, Term} = -- 'GajumaruSerialization':decode('GajumaruData', Binary), -- Term -- end. -- -- This works because: -- - All current top-level output from serialize() is an RLP list -- (first byte 0xC0-0xFF). -- - GajumaruData and the concrete choices above are SEQUENCEs -- (first byte 0x30 for short form, or 0x30 0x81/0x82... for long). -- -- 2. When you add a new object type or version, prefer adding a -- concrete SEQUENCE alternative in the Content CHOICE rather than -- always falling back to TemplateFields. This gives you better -- validation and generated types. -- -- 3. INTEGER in DER is signed and uses a different minimal encoding -- than the legacy unsigned big-endian no leading zero form. This is -- fine for new data. -- -- 4. If you need to preserve exact legacy integer wire bytes inside -- the new format (e.g. for some hash preimage reason), you can -- carry selected integers as OCTET STRING (legacy unsigned minimal bytes). -- -- 5. Dynamic encoding (gmser_dyn) is intentionally not modeled here -- in full, because it is runtime-schema driven (type codes 246-255, -- labels, alt/switch, etc.). You can still use the generic -- TemplateFields + Value for some dynamic cases, or model specific -- message schemas as additional CHOICE arms. -- -- 6. Compile with DER for canonical output: -- asn1ct:compile(GajumaruSerialization, [der]). -- -- The ber option is also accepted; der implies the stricter rules. END