feat(wire): add asset proof tags 49, 50, 51 readers

Reads asset_operation_proof, asset_operation_ownership_proof, and
asset_operation_ownership_proof_eth structures. All use CHAIN_TRANSITION_VER
with version byte prefix. Stored as opaque bytes.

Co-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
Claude 2026-03-16 20:55:33 +00:00
parent 3e79f34a65
commit d8e12a1539
No known key found for this signature in database
GPG key ID: AF404715446AEB41
2 changed files with 236 additions and 0 deletions

View file

@ -539,6 +539,11 @@ const (
// Asset descriptor operation (HF5).
tagAssetDescriptorOperation = 40 // asset_descriptor_operation
// Asset operation proof tags (HF5).
tagAssetOperationProof = 49 // asset_operation_proof
tagAssetOperationOwnershipProof = 50 // asset_operation_ownership_proof
tagAssetOperationOwnershipProofETH = 51 // asset_operation_ownership_proof_eth (Ethereum sig)
// Signature variant tags (signature_v).
tagNLSAGSig = 42 // NLSAG_sig — vector<signature>
tagZCSig = 43 // ZC_sig — 2 public_keys + CLSAG_GGX
@ -611,6 +616,14 @@ func readVariantElementData(dec *Decoder, tag uint8) []byte {
case tagAssetDescriptorOperation:
return readAssetDescriptorOperation(dec)
// Asset operation proof variants (HF5)
case tagAssetOperationProof:
return readAssetOperationProof(dec)
case tagAssetOperationOwnershipProof:
return readAssetOperationOwnershipProof(dec)
case tagAssetOperationOwnershipProofETH:
return readAssetOperationOwnershipProofETH(dec)
// Signature variants
case tagNLSAGSig: // vector<signature> (64 bytes each)
return readVariantVectorFixed(dec, 64)
@ -943,6 +956,109 @@ func readAssetDescriptorBase(dec *Decoder) []byte {
return raw
}
// readAssetOperationProof reads asset_operation_proof (tag 49).
// Structure (CHAIN_TRANSITION_VER, version 1):
//
// ver (uint8) + gss (generic_schnorr_sig_s: 64 bytes)
// + asset_id (32 bytes) + etc (vector<uint8>).
func readAssetOperationProof(dec *Decoder) []byte {
var raw []byte
// ver: uint8
ver := dec.ReadUint8()
if dec.err != nil {
return nil
}
raw = append(raw, ver)
// gss: generic_schnorr_sig_s — 2 scalars (s, c) = 64 bytes
b := dec.ReadBytes(64)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
// asset_id: 32-byte hash
b = dec.ReadBytes(32)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
// etc: vector<uint8>
v := readVariantVectorFixed(dec, 1)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
return raw
}
// readAssetOperationOwnershipProof reads asset_operation_ownership_proof (tag 50).
// Structure (CHAIN_TRANSITION_VER, version 1):
//
// ver (uint8) + gss (generic_schnorr_sig_s: 64 bytes)
// + etc (vector<uint8>).
func readAssetOperationOwnershipProof(dec *Decoder) []byte {
var raw []byte
// ver: uint8
ver := dec.ReadUint8()
if dec.err != nil {
return nil
}
raw = append(raw, ver)
// gss: generic_schnorr_sig_s — 2 scalars (s, c) = 64 bytes
b := dec.ReadBytes(64)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
// etc: vector<uint8>
v := readVariantVectorFixed(dec, 1)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
return raw
}
// readAssetOperationOwnershipProofETH reads asset_operation_ownership_proof_eth (tag 51).
// Structure (CHAIN_TRANSITION_VER, version 1):
//
// ver (uint8) + eth_sig (65 bytes: r(32) + s(32) + v(1))
// + etc (vector<uint8>).
func readAssetOperationOwnershipProofETH(dec *Decoder) []byte {
var raw []byte
// ver: uint8
ver := dec.ReadUint8()
if dec.err != nil {
return nil
}
raw = append(raw, ver)
// eth_sig: crypto::eth_signature — r(32) + s(32) + v(1) = 65 bytes
b := dec.ReadBytes(65)
if dec.err != nil {
return nil
}
raw = append(raw, b...)
// etc: vector<uint8>
v := readVariantVectorFixed(dec, 1)
if dec.err != nil {
return nil
}
raw = append(raw, v...)
return raw
}
// --- crypto blob readers ---
// These read variable-length serialised crypto structures and return raw bytes.
// All vectors are varint(count) + 32*count bytes (scalars or points).

View file

@ -137,3 +137,123 @@ func TestVariantVectorWithTag40_Good(t *testing.T) {
t.Fatalf("round-trip mismatch: got %d bytes, want %d bytes", len(got), len(raw))
}
}
func buildAssetOperationProofBlob() []byte {
var buf bytes.Buffer
enc := NewEncoder(&buf)
// ver: uint8 = 1
enc.WriteUint8(1)
// gss: generic_schnorr_sig_s — 2 scalars (s, c) = 64 bytes
enc.WriteBytes(make([]byte, 64))
// asset_id: 32-byte hash
enc.WriteBytes(bytes.Repeat([]byte{0xCD}, 32))
// etc: vector<uint8> (empty)
enc.WriteVarint(0)
return buf.Bytes()
}
func TestReadAssetOperationProof_Good(t *testing.T) {
blob := buildAssetOperationProofBlob()
dec := NewDecoder(bytes.NewReader(blob))
got := readAssetOperationProof(dec)
if dec.Err() != nil {
t.Fatalf("readAssetOperationProof failed: %v", dec.Err())
}
if !bytes.Equal(got, blob) {
t.Fatalf("round-trip mismatch: got %d bytes, want %d bytes", len(got), len(blob))
}
}
func TestReadAssetOperationProof_Bad(t *testing.T) {
dec := NewDecoder(bytes.NewReader([]byte{1}))
_ = readAssetOperationProof(dec)
if dec.Err() == nil {
t.Fatal("expected error for truncated asset operation proof")
}
}
func buildAssetOperationOwnershipProofBlob() []byte {
var buf bytes.Buffer
enc := NewEncoder(&buf)
// ver: uint8 = 1
enc.WriteUint8(1)
// gss: generic_schnorr_sig_s — 2 scalars = 64 bytes
enc.WriteBytes(make([]byte, 64))
// etc: vector<uint8> (empty)
enc.WriteVarint(0)
return buf.Bytes()
}
func TestReadAssetOperationOwnershipProof_Good(t *testing.T) {
blob := buildAssetOperationOwnershipProofBlob()
dec := NewDecoder(bytes.NewReader(blob))
got := readAssetOperationOwnershipProof(dec)
if dec.Err() != nil {
t.Fatalf("readAssetOperationOwnershipProof failed: %v", dec.Err())
}
if !bytes.Equal(got, blob) {
t.Fatalf("round-trip mismatch: got %d bytes, want %d bytes", len(got), len(blob))
}
}
func buildAssetOperationOwnershipProofETHBlob() []byte {
var buf bytes.Buffer
enc := NewEncoder(&buf)
// ver: uint8 = 1
enc.WriteUint8(1)
// eth_sig: 65 bytes (r=32 + s=32 + v=1)
enc.WriteBytes(make([]byte, 65))
// etc: vector<uint8> (empty)
enc.WriteVarint(0)
return buf.Bytes()
}
func TestReadAssetOperationOwnershipProofETH_Good(t *testing.T) {
blob := buildAssetOperationOwnershipProofETHBlob()
dec := NewDecoder(bytes.NewReader(blob))
got := readAssetOperationOwnershipProofETH(dec)
if dec.Err() != nil {
t.Fatalf("readAssetOperationOwnershipProofETH failed: %v", dec.Err())
}
if !bytes.Equal(got, blob) {
t.Fatalf("round-trip mismatch: got %d bytes, want %d bytes", len(got), len(blob))
}
}
func TestVariantVectorWithProofTags_Good(t *testing.T) {
// Build a variant vector with 3 elements: tags 49, 50, 51.
proofBlob := buildAssetOperationProofBlob()
ownershipBlob := buildAssetOperationOwnershipProofBlob()
ethBlob := buildAssetOperationOwnershipProofETHBlob()
var buf bytes.Buffer
enc := NewEncoder(&buf)
// count = 3
enc.WriteVarint(3)
// tag 49
enc.WriteUint8(tagAssetOperationProof)
enc.WriteBytes(proofBlob)
// tag 50
enc.WriteUint8(tagAssetOperationOwnershipProof)
enc.WriteBytes(ownershipBlob)
// tag 51
enc.WriteUint8(tagAssetOperationOwnershipProofETH)
enc.WriteBytes(ethBlob)
raw := buf.Bytes()
dec := NewDecoder(bytes.NewReader(raw))
got := decodeRawVariantVector(dec)
if dec.Err() != nil {
t.Fatalf("decodeRawVariantVector with proof tags failed: %v", dec.Err())
}
if !bytes.Equal(got, raw) {
t.Fatalf("round-trip mismatch: got %d bytes, want %d bytes", len(got), len(raw))
}
}