package state import ( "errors" "fmt" "math" "math/big" "git.marketally.com/tutus-one/tutus-chain/pkg/util" "git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem" ) // TokenStatus represents the status of a Vita token. type TokenStatus uint8 const ( // TokenStatusActive indicates an active token. TokenStatusActive TokenStatus = 0 // TokenStatusSuspended indicates a temporarily suspended token. TokenStatusSuspended TokenStatus = 1 // TokenStatusRevoked indicates a permanently revoked token. TokenStatusRevoked TokenStatus = 2 // TokenStatusRecovering indicates recovery is in progress. TokenStatusRecovering TokenStatus = 3 ) // DisclosureLevel represents the visibility level of an attribute. type DisclosureLevel uint8 const ( // DisclosurePrivate means only the owner can see the attribute. DisclosurePrivate DisclosureLevel = 0 // DisclosureVerifier means owner and designated verifiers can see it. DisclosureVerifier DisclosureLevel = 1 // DisclosureRole means owner and callers with specified roles can see it. DisclosureRole DisclosureLevel = 2 // DisclosurePublic means anyone can see the attribute. DisclosurePublic DisclosureLevel = 3 ) // RecoveryStatus represents the status of a recovery request. type RecoveryStatus uint8 const ( // RecoveryStatusPending indicates recovery is pending approval. RecoveryStatusPending RecoveryStatus = 0 // RecoveryStatusApproved indicates recovery has been approved. RecoveryStatusApproved RecoveryStatus = 1 // RecoveryStatusExecuted indicates recovery has been executed. RecoveryStatusExecuted RecoveryStatus = 2 // RecoveryStatusDenied indicates recovery has been denied. RecoveryStatusDenied RecoveryStatus = 3 // RecoveryStatusExpired indicates recovery request has expired. RecoveryStatusExpired RecoveryStatus = 4 ) // PresenceStatus represents the online presence status of a Vita holder. type PresenceStatus uint8 const ( // PresenceStatusOffline indicates no recent heartbeat. PresenceStatusOffline PresenceStatus = 0 // PresenceStatusOnline indicates active heartbeat within presence window. PresenceStatusOnline PresenceStatus = 1 // PresenceStatusAway indicates heartbeat within extended window but outside primary window. PresenceStatusAway PresenceStatus = 2 // PresenceStatusInvisible indicates heartbeats are sent but status is hidden. PresenceStatusInvisible PresenceStatus = 3 ) // Vita represents a soul-bound identity token. type Vita struct { TokenID uint64 // Unique sequential identifier Owner util.Uint160 // Owner's script hash PersonHash []byte // Hash of biometric/identity proof IsEntity bool // True if organization, false if natural person CreatedAt uint32 // Block height when created UpdatedAt uint32 // Block height of last modification Status TokenStatus // Current status StatusReason string // Reason for status change RecoveryHash []byte // Hash of recovery mechanism VestedUntil uint32 // Block height until which the Vita is vesting (Sybil resistance) } // ToStackItem implements stackitem.Convertible interface. func (t *Vita) ToStackItem() (stackitem.Item, error) { return stackitem.NewStruct([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(t.TokenID))), stackitem.NewByteArray(t.Owner.BytesBE()), stackitem.NewByteArray(t.PersonHash), stackitem.NewBool(t.IsEntity), stackitem.NewBigInteger(big.NewInt(int64(t.CreatedAt))), stackitem.NewBigInteger(big.NewInt(int64(t.UpdatedAt))), stackitem.NewBigInteger(big.NewInt(int64(t.Status))), stackitem.NewByteArray([]byte(t.StatusReason)), stackitem.NewByteArray(t.RecoveryHash), stackitem.NewBigInteger(big.NewInt(int64(t.VestedUntil))), }), nil } // FromStackItem implements stackitem.Convertible interface. func (t *Vita) FromStackItem(item stackitem.Item) error { items, ok := item.Value().([]stackitem.Item) if !ok { return errors.New("not a struct") } if len(items) != 10 { return fmt.Errorf("wrong number of elements: expected 10, got %d", len(items)) } tokenID, err := items[0].TryInteger() if err != nil { return fmt.Errorf("invalid tokenID: %w", err) } t.TokenID = tokenID.Uint64() ownerBytes, err := items[1].TryBytes() if err != nil { return fmt.Errorf("invalid owner: %w", err) } t.Owner, err = util.Uint160DecodeBytesBE(ownerBytes) if err != nil { return fmt.Errorf("invalid owner hash: %w", err) } t.PersonHash, err = items[2].TryBytes() if err != nil { return fmt.Errorf("invalid personHash: %w", err) } isEntity, err := items[3].TryBool() if err != nil { return fmt.Errorf("invalid isEntity: %w", err) } t.IsEntity = isEntity createdAt, err := items[4].TryInteger() if err != nil { return fmt.Errorf("invalid createdAt: %w", err) } t.CreatedAt = uint32(createdAt.Int64()) updatedAt, err := items[5].TryInteger() if err != nil { return fmt.Errorf("invalid updatedAt: %w", err) } t.UpdatedAt = uint32(updatedAt.Int64()) status, err := items[6].TryInteger() if err != nil { return fmt.Errorf("invalid status: %w", err) } t.Status = TokenStatus(status.Int64()) statusReasonBytes, err := items[7].TryBytes() if err != nil { return fmt.Errorf("invalid statusReason: %w", err) } t.StatusReason = string(statusReasonBytes) t.RecoveryHash, err = items[8].TryBytes() if err != nil { return fmt.Errorf("invalid recoveryHash: %w", err) } vestedUntil, err := items[9].TryInteger() if err != nil { return fmt.Errorf("invalid vestedUntil: %w", err) } t.VestedUntil = uint32(vestedUntil.Int64()) return nil } // Attribute represents an identity attribute with disclosure control. type Attribute struct { Key string // Attribute name ValueHash []byte // Hash of value (privacy) ValueEnc []byte // Encrypted value (optional) Attestor util.Uint160 // Who attested this (script hash) AttestedAt uint32 // Block height when attested ExpiresAt uint32 // Optional expiration (0 = never) Revoked bool // Whether attribute has been revoked RevokedAt uint32 // Block height when revoked DisclosureLevel DisclosureLevel // Visibility level } // ToStackItem implements stackitem.Convertible interface. func (a *Attribute) ToStackItem() (stackitem.Item, error) { return stackitem.NewStruct([]stackitem.Item{ stackitem.NewByteArray([]byte(a.Key)), stackitem.NewByteArray(a.ValueHash), stackitem.NewByteArray(a.ValueEnc), stackitem.NewByteArray(a.Attestor.BytesBE()), stackitem.NewBigInteger(big.NewInt(int64(a.AttestedAt))), stackitem.NewBigInteger(big.NewInt(int64(a.ExpiresAt))), stackitem.NewBool(a.Revoked), stackitem.NewBigInteger(big.NewInt(int64(a.RevokedAt))), stackitem.NewBigInteger(big.NewInt(int64(a.DisclosureLevel))), }), nil } // FromStackItem implements stackitem.Convertible interface. func (a *Attribute) FromStackItem(item stackitem.Item) error { items, ok := item.Value().([]stackitem.Item) if !ok { return errors.New("not a struct") } if len(items) != 9 { return fmt.Errorf("wrong number of elements: expected 9, got %d", len(items)) } keyBytes, err := items[0].TryBytes() if err != nil { return fmt.Errorf("invalid key: %w", err) } a.Key = string(keyBytes) a.ValueHash, err = items[1].TryBytes() if err != nil { return fmt.Errorf("invalid valueHash: %w", err) } a.ValueEnc, err = items[2].TryBytes() if err != nil { return fmt.Errorf("invalid valueEnc: %w", err) } attestorBytes, err := items[3].TryBytes() if err != nil { return fmt.Errorf("invalid attestor: %w", err) } a.Attestor, err = util.Uint160DecodeBytesBE(attestorBytes) if err != nil { return fmt.Errorf("invalid attestor hash: %w", err) } attestedAt, err := items[4].TryInteger() if err != nil { return fmt.Errorf("invalid attestedAt: %w", err) } a.AttestedAt = uint32(attestedAt.Int64()) expiresAt, err := items[5].TryInteger() if err != nil { return fmt.Errorf("invalid expiresAt: %w", err) } a.ExpiresAt = uint32(expiresAt.Int64()) a.Revoked, err = items[6].TryBool() if err != nil { return fmt.Errorf("invalid revoked: %w", err) } revokedAt, err := items[7].TryInteger() if err != nil { return fmt.Errorf("invalid revokedAt: %w", err) } a.RevokedAt = uint32(revokedAt.Int64()) disclosureLevel, err := items[8].TryInteger() if err != nil { return fmt.Errorf("invalid disclosureLevel: %w", err) } a.DisclosureLevel = DisclosureLevel(disclosureLevel.Int64()) return nil } // AuthChallenge represents a passwordless authentication challenge. type AuthChallenge struct { ChallengeID util.Uint256 // Unique challenge identifier TokenID uint64 // Associated Vita token Nonce []byte // Random bytes to sign CreatedAt uint32 // Block height when created ExpiresAt uint32 // Block height when expires Purpose string // "login", "sign", "recover" Fulfilled bool // Whether challenge has been fulfilled FulfilledAt uint32 // Block height when fulfilled } // ToStackItem implements stackitem.Convertible interface. func (c *AuthChallenge) ToStackItem() (stackitem.Item, error) { return stackitem.NewStruct([]stackitem.Item{ stackitem.NewByteArray(c.ChallengeID.BytesBE()), stackitem.NewBigInteger(big.NewInt(int64(c.TokenID))), stackitem.NewByteArray(c.Nonce), stackitem.NewBigInteger(big.NewInt(int64(c.CreatedAt))), stackitem.NewBigInteger(big.NewInt(int64(c.ExpiresAt))), stackitem.NewByteArray([]byte(c.Purpose)), stackitem.NewBool(c.Fulfilled), stackitem.NewBigInteger(big.NewInt(int64(c.FulfilledAt))), }), nil } // FromStackItem implements stackitem.Convertible interface. func (c *AuthChallenge) FromStackItem(item stackitem.Item) error { items, ok := item.Value().([]stackitem.Item) if !ok { return errors.New("not a struct") } if len(items) != 8 { return fmt.Errorf("wrong number of elements: expected 8, got %d", len(items)) } challengeIDBytes, err := items[0].TryBytes() if err != nil { return fmt.Errorf("invalid challengeID: %w", err) } c.ChallengeID, err = util.Uint256DecodeBytesBE(challengeIDBytes) if err != nil { return fmt.Errorf("invalid challengeID hash: %w", err) } tokenID, err := items[1].TryInteger() if err != nil { return fmt.Errorf("invalid tokenID: %w", err) } c.TokenID = tokenID.Uint64() c.Nonce, err = items[2].TryBytes() if err != nil { return fmt.Errorf("invalid nonce: %w", err) } createdAt, err := items[3].TryInteger() if err != nil { return fmt.Errorf("invalid createdAt: %w", err) } c.CreatedAt = uint32(createdAt.Int64()) expiresAt, err := items[4].TryInteger() if err != nil { return fmt.Errorf("invalid expiresAt: %w", err) } c.ExpiresAt = uint32(expiresAt.Int64()) purposeBytes, err := items[5].TryBytes() if err != nil { return fmt.Errorf("invalid purpose: %w", err) } c.Purpose = string(purposeBytes) c.Fulfilled, err = items[6].TryBool() if err != nil { return fmt.Errorf("invalid fulfilled: %w", err) } fulfilledAt, err := items[7].TryInteger() if err != nil { return fmt.Errorf("invalid fulfilledAt: %w", err) } c.FulfilledAt = uint32(fulfilledAt.Int64()) return nil } // RecoveryRequest represents a key recovery request. type RecoveryRequest struct { RequestID util.Uint256 // Unique request identifier TokenID uint64 // Token being recovered NewOwner util.Uint160 // Proposed new owner Requester util.Uint160 // Who initiated recovery Evidence []byte // Encrypted evidence hash Approvals []util.Uint160 // Approvers who have approved RequiredApprovals int // Required number of approvals CreatedAt uint32 // Block height when created DelayUntil uint32 // Block height when executable ExpiresAt uint32 // Block height when expires Status RecoveryStatus // Current status } // ToStackItem implements stackitem.Convertible interface. func (r *RecoveryRequest) ToStackItem() (stackitem.Item, error) { approvals := make([]stackitem.Item, len(r.Approvals)) for i, a := range r.Approvals { approvals[i] = stackitem.NewByteArray(a.BytesBE()) } return stackitem.NewStruct([]stackitem.Item{ stackitem.NewByteArray(r.RequestID.BytesBE()), stackitem.NewBigInteger(big.NewInt(int64(r.TokenID))), stackitem.NewByteArray(r.NewOwner.BytesBE()), stackitem.NewByteArray(r.Requester.BytesBE()), stackitem.NewByteArray(r.Evidence), stackitem.NewArray(approvals), stackitem.NewBigInteger(big.NewInt(int64(r.RequiredApprovals))), stackitem.NewBigInteger(big.NewInt(int64(r.CreatedAt))), stackitem.NewBigInteger(big.NewInt(int64(r.DelayUntil))), stackitem.NewBigInteger(big.NewInt(int64(r.ExpiresAt))), stackitem.NewBigInteger(big.NewInt(int64(r.Status))), }), nil } // FromStackItem implements stackitem.Convertible interface. func (r *RecoveryRequest) FromStackItem(item stackitem.Item) error { items, ok := item.Value().([]stackitem.Item) if !ok { return errors.New("not a struct") } if len(items) != 11 { return fmt.Errorf("wrong number of elements: expected 11, got %d", len(items)) } requestIDBytes, err := items[0].TryBytes() if err != nil { return fmt.Errorf("invalid requestID: %w", err) } r.RequestID, err = util.Uint256DecodeBytesBE(requestIDBytes) if err != nil { return fmt.Errorf("invalid requestID hash: %w", err) } tokenID, err := items[1].TryInteger() if err != nil { return fmt.Errorf("invalid tokenID: %w", err) } r.TokenID = tokenID.Uint64() newOwnerBytes, err := items[2].TryBytes() if err != nil { return fmt.Errorf("invalid newOwner: %w", err) } r.NewOwner, err = util.Uint160DecodeBytesBE(newOwnerBytes) if err != nil { return fmt.Errorf("invalid newOwner hash: %w", err) } requesterBytes, err := items[3].TryBytes() if err != nil { return fmt.Errorf("invalid requester: %w", err) } r.Requester, err = util.Uint160DecodeBytesBE(requesterBytes) if err != nil { return fmt.Errorf("invalid requester hash: %w", err) } r.Evidence, err = items[4].TryBytes() if err != nil { return fmt.Errorf("invalid evidence: %w", err) } approvalsArray, ok := items[5].Value().([]stackitem.Item) if !ok { return errors.New("approvals is not an array") } r.Approvals = make([]util.Uint160, len(approvalsArray)) for i, a := range approvalsArray { approvalBytes, err := a.TryBytes() if err != nil { return fmt.Errorf("invalid approval %d: %w", i, err) } r.Approvals[i], err = util.Uint160DecodeBytesBE(approvalBytes) if err != nil { return fmt.Errorf("invalid approval %d hash: %w", i, err) } } requiredApprovals, err := items[6].TryInteger() if err != nil { return fmt.Errorf("invalid requiredApprovals: %w", err) } ra := requiredApprovals.Int64() if ra < 0 || ra > math.MaxInt { return errors.New("requiredApprovals out of range") } r.RequiredApprovals = int(ra) createdAt, err := items[7].TryInteger() if err != nil { return fmt.Errorf("invalid createdAt: %w", err) } r.CreatedAt = uint32(createdAt.Int64()) delayUntil, err := items[8].TryInteger() if err != nil { return fmt.Errorf("invalid delayUntil: %w", err) } r.DelayUntil = uint32(delayUntil.Int64()) expiresAt, err := items[9].TryInteger() if err != nil { return fmt.Errorf("invalid expiresAt: %w", err) } r.ExpiresAt = uint32(expiresAt.Int64()) status, err := items[10].TryInteger() if err != nil { return fmt.Errorf("invalid status: %w", err) } r.Status = RecoveryStatus(status.Int64()) return nil } // PresenceRecord represents a VPP (Vita Presence Protocol) heartbeat record. // Used for real-time proof of humanity in online applications. type PresenceRecord struct { TokenID uint64 // Associated Vita token LastHeartbeat uint32 // Block height of last heartbeat SessionID []byte // Current session identifier (32 bytes) DeviceHash []byte // Hash of device fingerprint (privacy) Status PresenceStatus // Current visibility status Invisible bool // If true, heartbeats are recorded but status is hidden } // ToStackItem implements stackitem.Convertible interface. func (p *PresenceRecord) ToStackItem() (stackitem.Item, error) { return stackitem.NewStruct([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(p.TokenID))), stackitem.NewBigInteger(big.NewInt(int64(p.LastHeartbeat))), stackitem.NewByteArray(p.SessionID), stackitem.NewByteArray(p.DeviceHash), stackitem.NewBigInteger(big.NewInt(int64(p.Status))), stackitem.NewBool(p.Invisible), }), nil } // FromStackItem implements stackitem.Convertible interface. func (p *PresenceRecord) FromStackItem(item stackitem.Item) error { items, ok := item.Value().([]stackitem.Item) if !ok { return errors.New("not a struct") } if len(items) != 6 { return fmt.Errorf("wrong number of elements: expected 6, got %d", len(items)) } tokenID, err := items[0].TryInteger() if err != nil { return fmt.Errorf("invalid tokenID: %w", err) } p.TokenID = tokenID.Uint64() lastHeartbeat, err := items[1].TryInteger() if err != nil { return fmt.Errorf("invalid lastHeartbeat: %w", err) } p.LastHeartbeat = uint32(lastHeartbeat.Int64()) p.SessionID, err = items[2].TryBytes() if err != nil { return fmt.Errorf("invalid sessionID: %w", err) } p.DeviceHash, err = items[3].TryBytes() if err != nil { return fmt.Errorf("invalid deviceHash: %w", err) } status, err := items[4].TryInteger() if err != nil { return fmt.Errorf("invalid status: %w", err) } p.Status = PresenceStatus(status.Int64()) p.Invisible, err = items[5].TryBool() if err != nil { return fmt.Errorf("invalid invisible: %w", err) } return nil } // PresenceConfig holds configuration for the VPP presence system. type PresenceConfig struct { // PresenceWindow is the number of blocks within which a heartbeat is considered "online". // Default: 60 blocks (~1 minute at 1 second/block) PresenceWindow uint32 // AwayWindow is the extended window for "away" status. // Default: 300 blocks (~5 minutes) AwayWindow uint32 // MinHeartbeatInterval is the minimum blocks between heartbeats (rate limiting). // Default: 15 blocks (~15 seconds) MinHeartbeatInterval uint32 // MaxHeartbeatInterval is the maximum blocks before a heartbeat is required. // Default: 60 blocks (~1 minute) MaxHeartbeatInterval uint32 } // ToStackItem implements stackitem.Convertible interface. func (c *PresenceConfig) ToStackItem() (stackitem.Item, error) { return stackitem.NewStruct([]stackitem.Item{ stackitem.NewBigInteger(big.NewInt(int64(c.PresenceWindow))), stackitem.NewBigInteger(big.NewInt(int64(c.AwayWindow))), stackitem.NewBigInteger(big.NewInt(int64(c.MinHeartbeatInterval))), stackitem.NewBigInteger(big.NewInt(int64(c.MaxHeartbeatInterval))), }), nil } // FromStackItem implements stackitem.Convertible interface. func (c *PresenceConfig) FromStackItem(item stackitem.Item) error { items, ok := item.Value().([]stackitem.Item) if !ok { return errors.New("not a struct") } if len(items) != 4 { return fmt.Errorf("wrong number of elements: expected 4, got %d", len(items)) } presenceWindow, err := items[0].TryInteger() if err != nil { return fmt.Errorf("invalid presenceWindow: %w", err) } c.PresenceWindow = uint32(presenceWindow.Int64()) awayWindow, err := items[1].TryInteger() if err != nil { return fmt.Errorf("invalid awayWindow: %w", err) } c.AwayWindow = uint32(awayWindow.Int64()) minHeartbeat, err := items[2].TryInteger() if err != nil { return fmt.Errorf("invalid minHeartbeatInterval: %w", err) } c.MinHeartbeatInterval = uint32(minHeartbeat.Int64()) maxHeartbeat, err := items[3].TryInteger() if err != nil { return fmt.Errorf("invalid maxHeartbeatInterval: %w", err) } c.MaxHeartbeatInterval = uint32(maxHeartbeat.Int64()) return nil } // DefaultPresenceConfig returns the default VPP configuration. func DefaultPresenceConfig() PresenceConfig { return PresenceConfig{ PresenceWindow: 60, // ~1 minute AwayWindow: 300, // ~5 minutes MinHeartbeatInterval: 15, // ~15 seconds MaxHeartbeatInterval: 60, // ~1 minute } }