diff --git a/wire/transaction.go b/wire/transaction.go index 2af5be2..eb24f42 100644 --- a/wire/transaction.go +++ b/wire/transaction.go @@ -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 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 (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). +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 + 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). +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 + 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). +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 + 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). diff --git a/wire/transaction_v3_test.go b/wire/transaction_v3_test.go index 3a208d1..ae08404 100644 --- a/wire/transaction_v3_test.go +++ b/wire/transaction_v3_test.go @@ -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 (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 (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 (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)) + } +}