248 lines
8.7 KiB
Plaintext
248 lines
8.7 KiB
Plaintext
-- 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 for portability
|
|
-- (other languages use ASN.1 compilers to get types/parsers).
|
|
-- * Define compact canonical wire format using standard ASN.1 techniques
|
|
-- (primarily Unaligned PER / UPER with constraints for packing).
|
|
-- * Static encoding only (templates from gmserialization).
|
|
-- * Legacy RLP compatibility is achieved via separate translation layer
|
|
-- (deferred in current focus).
|
|
--
|
|
-- 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 for compact wire):
|
|
-- {ok, Mod} = asn1ct:compile(GajumaruSerialization.asn, [uper]).
|
|
-- {ok, Bytes} = 'GajumaruSerialization':encode('GajumaruData', Asn1Value).
|
|
-- Bytes is the compact UPER wire format (deterministic with this schema).
|
|
-- For legacy RLP, use the model-to-RLP layer instead (see gmser_asn1_rlp).
|
|
--
|
|
-- 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 {
|
|
-- Constrained for better PER packing
|
|
tag INTEGER (0..65535),
|
|
vsn INTEGER (0..255),
|
|
content Content
|
|
}
|
|
|
|
-- Preferred top-level for the compact static wire format.
|
|
-- Avoids the extra Content CHOICE tag when the structure is known to be static.
|
|
CompactStatic ::= SEQUENCE {
|
|
tag INTEGER (0..65535),
|
|
vsn INTEGER (0..255),
|
|
fields StaticFields
|
|
}
|
|
|
|
-- 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,
|
|
|
|
-- Static-optimized: no field names on wire (matches legacy static behavior exactly)
|
|
-- Preferred for compact wire format of known templates.
|
|
staticFields [10] StaticFields,
|
|
|
|
-- 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
|
|
}
|
|
|
|
-- Optimized for static wire format: just the values in order, no names.
|
|
-- This matches the legacy static encoding where maps/records are positional only.
|
|
StaticFields ::= SEQUENCE OF 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
|
|
-- <<B, _/binary>> 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. For the compact wire format we use UPER (unaligned PER).
|
|
-- It is stable/deterministic for a given schema (no extensibility
|
|
-- markers on these types, fixed order).
|
|
--
|
|
-- 4. INTEGER uses ASN.1 PER encoding (canonical for the constraints).
|
|
-- This may differ from legacy RLP minimal unsigned; new format
|
|
-- will have different hashes (expected when introducing new encoding).
|
|
--
|
|
-- 5. Dynamic encoding (gmser_dyn) is not in scope here.
|
|
--
|
|
-- 6. To generate the compact wire:
|
|
-- asn1ct:compile(GajumaruSerialization, [uper]).
|
|
-- {ok, CompactBytes} = 'GajumaruSerialization':encode('GajumaruData', Value).
|
|
--
|
|
-- Use staticFields (not templateFields) for best compactness on static data.
|
|
|
|
END
|