diff --git a/account/mask.go b/account/mask.go new file mode 100644 index 0000000..1a71800 --- /dev/null +++ b/account/mask.go @@ -0,0 +1,48 @@ +package account + +import "fmt" + +var ( + ErrInvalidAddress = fmt.Errorf("invalid Ethereum address") + ErrInvalidNumVisibleChars = fmt.Errorf("number of visible characters must be even") + ErrInvalidMaskedChars = fmt.Errorf("invalid number of masked characters") +) + +// MaskAddress masks the middle part of an Ethereum address with a number of masked characters and returns the masked address. +// The number of visible characters must be even. +// The number of masked characters must be greater than 0. +// The masked character can be any character. +// @param address: The Ethereum address to mask +// @param numVisibleChars: The number of visible characters to keep at the start and end of the address +// @param maskedCharacter: The character to use for masking the middle part of the address +// @return The masked address and error if any +func MaskAddress(address string, numVisibleChars int, maskedCharacter rune) (string, error) { + if !IsValidAddress(address) { + return "", ErrInvalidAddress + } + + if numVisibleChars%2 != 0 || numVisibleChars <= 0 { + return "", ErrInvalidNumVisibleChars + } + + prefixLength := len("0x") + numVisibleChars/2 // The number of characters to keep at the start (0x + first some hex digits) + suffixLength := numVisibleChars / 2 // The number of characters to keep at the end (last some hex digits) + + addressLength := len(address) + maskedCharacterCount := addressLength - prefixLength - suffixLength + + if maskedCharacterCount <= 0 { + return "", ErrInvalidMaskedChars + } + + // Create the masked part + maskedPart := make([]rune, maskedCharacterCount) + for i := range maskedPart { + maskedPart[i] = maskedCharacter + } + + // Construct the masked address + maskedAddress := address[:prefixLength] + string(maskedPart) + address[addressLength-suffixLength:] + + return maskedAddress, nil +} diff --git a/account/mask_test.go b/account/mask_test.go new file mode 100644 index 0000000..3c78def --- /dev/null +++ b/account/mask_test.go @@ -0,0 +1,84 @@ +package account + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMaskAddress(t *testing.T) { + type args struct { + address string + numVisibleChars int + maskedCharacter rune + } + tests := []struct { + name string + args args + want string + wantErr error + }{ + { + name: "valid address", + args: args{ + address: "0x1234567890abcdef1234567890abcdef12345678", + numVisibleChars: 8, + maskedCharacter: 'x', + }, + want: "0x1234xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx5678", + wantErr: nil, + }, + { + name: "invalid address", + args: args{ + address: "0x1234567890abcdef1234567890abcdef1234567", + numVisibleChars: 8, + maskedCharacter: 'x', + }, + want: "", + wantErr: ErrInvalidAddress, + }, + { + name: "invalid number of visible characters", + args: args{ + address: "0x1234567890abcdef1234567890abcdef12345678", + numVisibleChars: 7, + maskedCharacter: 'x', + }, + want: "", + wantErr: ErrInvalidNumVisibleChars, + }, + { + name: "invalid number of visible characters", + args: args{ + address: "0x1234567890abcdef1234567890abcdef12345678", + numVisibleChars: -1, + maskedCharacter: 'x', + }, + want: "", + wantErr: ErrInvalidNumVisibleChars, + }, + { + name: "invalid number of masked characters", + args: args{ + address: "0x1234567890abcdef1234567890abcdef12345678", + numVisibleChars: 40, + maskedCharacter: 'x', + }, + want: "", + wantErr: ErrInvalidMaskedChars, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := MaskAddress(tt.args.address, tt.args.numVisibleChars, tt.args.maskedCharacter) + if err != nil { + assert.ErrorIsf(t, err, tt.wantErr, "MaskAddress() error = %v, wantErr %v", err, tt.wantErr) + return + } + + assert.Equalf(t, tt.want, got, "MaskAddress() = %v, want %v", got, tt.want) + }) + } +}