2026-02-20 23:17:22 +00:00
|
|
|
// Copyright (c) 2017-2026 Lethean (https://lt.hn)
|
|
|
|
|
//
|
|
|
|
|
// Licensed under the European Union Public Licence (EUPL) version 1.2.
|
|
|
|
|
// You may obtain a copy of the licence at:
|
|
|
|
|
//
|
|
|
|
|
// https://joinup.ec.europa.eu/software/page/eupl/licence-eupl
|
|
|
|
|
//
|
|
|
|
|
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
|
|
|
|
|
|
package wallet
|
|
|
|
|
|
|
|
|
|
import (
|
2026-03-22 01:49:26 +00:00
|
|
|
"dappco.re/go/core/blockchain/crypto"
|
|
|
|
|
"dappco.re/go/core/blockchain/types"
|
2026-02-20 23:17:22 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Scanner detects outputs belonging to a wallet within a transaction.
|
|
|
|
|
type Scanner interface {
|
|
|
|
|
ScanTransaction(tx *types.Transaction, txHash types.Hash,
|
|
|
|
|
blockHeight uint64, extra *TxExtra) ([]Transfer, error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// V1Scanner implements Scanner for v0/v1 transactions using ECDH derivation.
|
|
|
|
|
// For each output it performs: derivation = viewSecret * txPubKey, then checks
|
|
|
|
|
// whether DerivePublicKey(derivation, i, spendPub) matches the output key.
|
|
|
|
|
type V1Scanner struct {
|
|
|
|
|
account *Account
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewV1Scanner returns a scanner bound to the given account.
|
|
|
|
|
func NewV1Scanner(acc *Account) *V1Scanner {
|
|
|
|
|
return &V1Scanner{account: acc}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ScanTransaction examines every output in tx and returns a Transfer for each
|
|
|
|
|
// output that belongs to the scanner's account. The caller must supply a
|
|
|
|
|
// pre-parsed TxExtra so that the tx public key is available.
|
|
|
|
|
func (s *V1Scanner) ScanTransaction(tx *types.Transaction, txHash types.Hash,
|
|
|
|
|
blockHeight uint64, extra *TxExtra) ([]Transfer, error) {
|
|
|
|
|
|
|
|
|
|
if extra.TxPublicKey.IsZero() {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
derivation, err := crypto.GenerateKeyDerivation(
|
|
|
|
|
[32]byte(extra.TxPublicKey),
|
|
|
|
|
[32]byte(s.account.ViewSecretKey))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isCoinbase := len(tx.Vin) > 0 && tx.Vin[0].InputType() == types.InputTypeGenesis
|
|
|
|
|
|
|
|
|
|
var transfers []Transfer
|
2026-04-04 20:36:16 +00:00
|
|
|
for i, output := range tx.Vout {
|
|
|
|
|
bare, ok := output.(types.TxOutputBare)
|
2026-02-20 23:17:22 +00:00
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 20:36:16 +00:00
|
|
|
expectedPublicKey, err := crypto.DerivePublicKey(
|
2026-02-20 23:17:22 +00:00
|
|
|
derivation, uint64(i), [32]byte(s.account.SpendPublicKey))
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 22:00:07 +00:00
|
|
|
targetKey, ok := bare.SpendKey()
|
2026-03-16 20:23:27 +00:00
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2026-04-04 22:00:07 +00:00
|
|
|
if types.PublicKey(expectedPublicKey) != targetKey {
|
2026-02-20 23:17:22 +00:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 20:36:16 +00:00
|
|
|
ephemeralSecretKey, err := crypto.DeriveSecretKey(
|
2026-02-20 23:17:22 +00:00
|
|
|
derivation, uint64(i), [32]byte(s.account.SpendSecretKey))
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-04 20:36:16 +00:00
|
|
|
keyImage, err := crypto.GenerateKeyImage(expectedPublicKey, ephemeralSecretKey)
|
2026-02-20 23:17:22 +00:00
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
transfers = append(transfers, Transfer{
|
|
|
|
|
TxHash: txHash,
|
|
|
|
|
OutputIndex: uint32(i),
|
|
|
|
|
Amount: bare.Amount,
|
|
|
|
|
BlockHeight: blockHeight,
|
|
|
|
|
EphemeralKey: KeyPair{
|
2026-04-04 20:36:16 +00:00
|
|
|
Public: types.PublicKey(expectedPublicKey),
|
|
|
|
|
Secret: types.SecretKey(ephemeralSecretKey),
|
2026-02-20 23:17:22 +00:00
|
|
|
},
|
2026-04-04 20:36:16 +00:00
|
|
|
KeyImage: types.KeyImage(keyImage),
|
2026-02-20 23:17:22 +00:00
|
|
|
Coinbase: isCoinbase,
|
|
|
|
|
UnlockTime: extra.UnlockTime,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return transfers, nil
|
|
|
|
|
}
|