Skip to content

Commit

Permalink
Add exchange address support (#297)
Browse files Browse the repository at this point in the history
* Add exchange address support

* Fix exchange address

* Fix comment
  • Loading branch information
Duddino authored Feb 23, 2024
1 parent 72aed81 commit 84eb64d
Show file tree
Hide file tree
Showing 22 changed files with 212 additions and 28 deletions.
10 changes: 6 additions & 4 deletions chain_params.prod.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
"TICKER": "PIV",
"PUBKEY_PREFIX": ["D"],
"STAKING_PREFIX": "S",
"PUBKEY_ADDRESS": 30,
"STAKING_ADDRESS": 63,
"PUBKEY_ADDRESS": [30],
"EXCHANGE_ADDRESS_PREFIX": [1, 185, 162],
"STAKING_ADDRESS": [63],
"SECRET_KEY": 212,
"BIP44_TYPE": 119,
"BIP44_TYPE_LEDGER": 77,
Expand Down Expand Up @@ -36,9 +37,10 @@
"isTestnet": true,
"TICKER": "tPIV",
"PUBKEY_PREFIX": ["x", "y"],
"EXCHANGE_ADDRESS_PREFIX": [1, 185, 177],
"STAKING_PREFIX": "W",
"PUBKEY_ADDRESS": 139,
"STAKING_ADDRESS": 73,
"PUBKEY_ADDRESS": [139],
"STAKING_ADDRESS": [73],
"SECRET_KEY": 239,
"BIP44_TYPE": 1,
"BIP44_TYPE_LEDGER": 1,
Expand Down
1 change: 1 addition & 0 deletions locale/cnr/translation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ chartImmatureBalance = "" # Immature balance
useShieldInputs = "" # Use shield inputs
newShieldAddress = "" # Get new shield address
shieldAddress = "" # Shield address
cantShieldToExc = "" # This address does not support shield transfers

[ALERTS]
INTERNAL_ERROR = "Interna greška, molimo pokušajte ponovo kasnije" # Internal error, please try again later
Expand Down
1 change: 1 addition & 0 deletions locale/de/translation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ syncShieldProgress = "" # Loading shielded blocks {current} of {total}
useShieldInputs = "" # Use shield inputs
newShieldAddress = "" # Get new shield address
shieldAddress = "" # Shield address
cantShieldToExc = "" # This address does not support shield transfers

[ALERTS]
INTERNAL_ERROR = "Interner Fehler, bitte versuche es später erneut" # Internal error, please try again later
Expand Down
1 change: 1 addition & 0 deletions locale/en/translation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ syncShieldProgress = "Loading shielded blocks {current} of {total}" # Loading sh
useShieldInputs = "Use shield inputs" # Use shield inputs
newShieldAddress = "Get new shield address" # Get new shield address
shieldAddress = "Shield address" # Shield address
cantShieldToExc = "This address does not support shield transfers" # This address does not support shield transfers

[ALERTS]
INTERNAL_ERROR = "Internal error, please try again later" # Internal error, please try again later
Expand Down
1 change: 1 addition & 0 deletions locale/es-mx/translation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ syncShieldProgress = "" # Loading shielded blocks {current} of {total}
useShieldInputs = "" # Use shield inputs
newShieldAddress = "" # Get new shield address
shieldAddress = "" # Shield address
cantShieldToExc = "" # This address does not support shield transfers

[ALERTS]
INTERNAL_ERROR = "Error interno, vuelve a intentarlo más tarde" # Internal error, please try again later
Expand Down
1 change: 1 addition & 0 deletions locale/fr/translation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ syncShieldProgress = "" # Loading shielded blocks {current} of {total}
useShieldInputs = "" # Use shield inputs
newShieldAddress = "" # Get new shield address
shieldAddress = "" # Shield address
cantShieldToExc = "" # This address does not support shield transfers

[ALERTS]
INTERNAL_ERROR = "Erreur interne, veuillez réessayer plus tard" # Internal error, please try again later
Expand Down
1 change: 1 addition & 0 deletions locale/it/translation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ syncShieldProgress = "" # Loading shielded blocks {current} of {total}
useShieldInputs = "" # Use shield inputs
newShieldAddress = "" # Get new shield address
shieldAddress = "" # Shield address
cantShieldToExc = "" # This address does not support shield transfers

[ALERTS]
INTERNAL_ERROR = "Errore interno, rirova più tardi" # Internal error, please try again later
Expand Down
1 change: 1 addition & 0 deletions locale/nl/translation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ syncShieldProgress = "" # Loading shielded blocks {current} of {total}
useShieldInputs = "" # Use shield inputs
newShieldAddress = "" # Get new shield address
shieldAddress = "" # Shield address
cantShieldToExc = "" # This address does not support shield transfers

[ALERTS]
INTERNAL_ERROR = "Interne fout, probeer het later opnieuw" # Internal error, please try again later
Expand Down
1 change: 1 addition & 0 deletions locale/ph/translation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ syncShieldProgress = "" # Loading shielded blocks {current} of {total}
useShieldInputs = "" # Use shield inputs
newShieldAddress = "" # Get new shield address
shieldAddress = "" # Shield address
cantShieldToExc = "" # This address does not support shield transfers

[ALERTS]
INTERNAL_ERROR = "Internal error, Pakiusap uliting muli" # Internal error, please try again later
Expand Down
1 change: 1 addition & 0 deletions locale/pl/translation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ chartImmatureBalance = "" # Immature balance
useShieldInputs = "" # Use shield inputs
newShieldAddress = "" # Get new shield address
shieldAddress = "" # Shield address
cantShieldToExc = "" # This address does not support shield transfers

[ALERTS]
INTERNAL_ERROR = "Błąd wewnętrzny, spróbuj ponownie później" # Internal error, please try again later
Expand Down
1 change: 1 addition & 0 deletions locale/pt-br/translation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ syncShieldProgress = "" # Loading shielded blocks {current} of {total}
useShieldInputs = "" # Use shield inputs
newShieldAddress = "" # Get new shield address
shieldAddress = "" # Shield address
cantShieldToExc = "" # This address does not support shield transfers

[ALERTS]
STAKE_ADDR_SET = "<b>Endereço de Cold Staking definido!</b><br>Ao fazer Stake no futuro este endereço irá ser usado." # <b>Cold Address set!</b><br>Future stakes will use this address.
Expand Down
1 change: 1 addition & 0 deletions locale/pt-pt/translation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ syncShieldProgress = "" # Loading shielded blocks {current} of {total}
useShieldInputs = "" # Use shield inputs
newShieldAddress = "" # Get new shield address
shieldAddress = "" # Shield address
cantShieldToExc = "" # This address does not support shield transfers

[ALERTS]
STAKE_ADDR_SET = "<b>Endereço de Cold Staking definido!</b><br>Ao fazer Stake no futuro irá ser usado este endereço." # <b>Cold Address set!</b><br>Future stakes will use this address.
Expand Down
1 change: 1 addition & 0 deletions locale/template/translation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ unhandledException = "Unhandled exception."
useShieldInputs = "Use shield inputs"
newShieldAddress = "Get new shield address"
shieldAddress = "Shield address"
cantShieldToExc = "This address does not support shield transfers"

[ALERTS]
INTERNAL_ERROR = "Internal error, please try again later"
Expand Down
1 change: 1 addition & 0 deletions locale/uwu/translation.toml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ syncShieldProgress = "" # Loading shielded blocks {current} of {total}
useShieldInputs = "" # Use shield inputs
newShieldAddress = "" # Get new shield address
shieldAddress = "" # Shield address
cantShieldToExc = "" # This address does not support shield transfers

[ALERTS]
INTERNAL_ERROR = "Internal error, pwease try again later" # Internal error, please try again later
Expand Down
14 changes: 13 additions & 1 deletion scripts/dashboard/Dashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import { parseWIF, verifyWIF } from '../encoding.js';
import {
createAlert,
isBase64,
isExchangeAddress,
isShieldAddress,
isValidPIVXAddress,
parseBIP21Request,
sanitizeHTML,
} from '../misc.js';
Expand Down Expand Up @@ -393,12 +395,22 @@ async function send(address, amount, useShieldInputs) {
// Check if the Receiver Address is a valid P2PKH address
// or shield address
if (!isStandardAddress(address) && !isShieldAddress(address))
if (!isValidPIVXAddress(address))
return createAlert(
'warning',
tr(ALERTS.INVALID_ADDRESS, [{ address }]),
2500
);
if (isColdAddress(address)) {
return createAlert(
'warning',
tr(ALERTS.INVALID_ADDRESS, [{ address }]),
2500
);
}
if (isExchangeAddress(address) && useShieldInputs) {
return createAlert('warning', translation.cantShieldToExc, 2500);
}
// Sanity check the amount
const nValue = Math.round(amount * COIN);
Expand Down
10 changes: 5 additions & 5 deletions scripts/encoding.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export function deriveAddress({ pkBytes, publicKey, output = 'ENCODED' }) {
/**
* Verify the integrity of an address
* @param {string} strPubkey - A base58 encoded public key
* @param {Object} [expectedKey] - The key type to check, defaults to current chain's `PUBKEY_ADDRESS`
* @param {number[]} [expectedKey] - The key type to check, defaults to current chain's `PUBKEY_ADDRESS`
* @return {boolean|Error} `true` if valid, `false` if invalid
*/
export function verifyPubkey(
Expand All @@ -141,15 +141,15 @@ export function verifyPubkey(
// Decode base58 and verify basic integrity
try {
const bDecoded = bs58.decode(strPubkey);
if (bDecoded.length !== 25) return false;
if (bDecoded[0] !== expectedKey) return false;
if (bDecoded.length !== 25 && bDecoded.length !== 27) return false;
if (!expectedKey.every((n, i) => n === bDecoded[i])) return false;

// Sha256d hash the pubkey payload
const bDoubleSHA = dSHA256(bDecoded.slice(0, 21));
const bDoubleSHA = dSHA256(bDecoded.slice(0, bDecoded.length - 4));

// Verify payload integrity via checksum
const bChecksum = bDoubleSHA.slice(0, 4);
const bChecksumPayload = bDecoded.slice(21);
const bChecksumPayload = bDecoded.slice(bDecoded.length - 4);
if (!bChecksum.every((byte, i) => byte === bChecksumPayload[i]))
return false;

Expand Down
15 changes: 14 additions & 1 deletion scripts/misc.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,18 @@ export function isColdAddress(strAddress) {
return verifyPubkey(strAddress, cChainParams.current.STAKING_ADDRESS);
}

/**
* A quick check to see if an address is an exchange address
* @param {string} strAddress - The address to check
* @returns {boolean} - `true` if an exchange address, `false` if not
*/
export function isExchangeAddress(strAddress) {
return verifyPubkey(
strAddress,
cChainParams.current.EXCHANGE_ADDRESS_PREFIX
);
}

/**
* @param {string} strAddress - The address to check
* @returns {boolean} if strAddress is a valid shiled address
Expand All @@ -259,7 +271,8 @@ export function isValidPIVXAddress(strAddress) {
return (
isStandardAddress(strAddress) ||
isColdAddress(strAddress) ||
isShieldAddress(strAddress)
isShieldAddress(strAddress) ||
isExchangeAddress(strAddress)
);
}

Expand Down
1 change: 1 addition & 0 deletions scripts/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export const OP = {
// cold staking
CHECKCOLDSTAKEVERIFY_LOF: 0xd1, // last output free for masternode/budget payments
CHECKCOLDSTAKEVERIFY: 0xd2,
EXCHANGEADDR: 0xe0,

INVALIDOPCODE: 0xff,
};
Expand Down
62 changes: 47 additions & 15 deletions scripts/transaction_builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Transaction, CTxIn, CTxOut, COutpoint } from './transaction.js';
import bs58 from 'bs58';
import { OP } from './script.js';
import { hexToBytes, bytesToHex, dSHA256 } from './utils.js';
import { isShieldAddress } from './misc.js';
import { isShieldAddress, isExchangeAddress } from './misc.js';
/**
* @class Builds a non-signed transaction
*/
Expand Down Expand Up @@ -95,7 +95,7 @@ export class TransactionBuilder {
const back = bytes.slice(bytes.length - 4);
const checksum = dSHA256(front).slice(0, 4);
if (checksum + '' == back + '') {
return Array.from(front.slice(1));
return Array.from(front.slice(isExchangeAddress(address) ? 3 : 1));
}
throw new Error('Invalid address');
}
Expand All @@ -117,31 +117,65 @@ export class TransactionBuilder {
}

/**
* Adds a P2PKH output to the transaction
* @param {{address: string, value: number}}
* @returns {TransactionBuilder}
* Add an exchange output to the transaction.
* @param{{address:string, value: number}}
*/
addOutput({ address, value }) {
if (isShieldAddress(address)) {
return this.#addShieldOutput({ address, value });
}
#addExchangeOutput({ address, value }) {
const decoded = this.#decodeAddress(address);
const script = [
OP['EXCHANGEADDR'],
OP['DUP'],
OP['HASH160'],
decoded.length,
...decoded,
OP['EQUALVERIFY'],
OP['CHECKSIG'],
];
this.#addScript({ script, value });
}

#addScript({ script, value }) {
this.#transaction.vout.push(
new CTxOut({
script: bytesToHex(script),
value,
})
);
this.#valueOut += value;
}

/**
* Adds a P2PKH output to the transaction
* @param {{address: string, value: number}}
* @returns {TransactionBuilder}
*/
#addP2pkhOutput({ address, value }) {
const decoded = this.#decodeAddress(address);
const script = [
OP['DUP'],
OP['HASH160'],
decoded.length,
...decoded,
OP['EQUALVERIFY'],
OP['CHECKSIG'],
];
this.#addScript({ script, value });
}

/**
* Adds an output to the transaction
* @param {{address: string, value: number}}
* @returns {TransactionBuilder}
*/
addOutput({ address, value }) {
if (isShieldAddress(address)) {
this.#addShieldOutput({ address, value });
} else if (isExchangeAddress(address)) {
this.#addExchangeOutput({ address, value });
} else {
this.#addP2pkhOutput({ address, value });
}

return this;
}

Expand All @@ -158,12 +192,10 @@ export class TransactionBuilder {
* @returns {TransactionBuilder}
*/
addProposalOutput({ hash, value }) {
this.#transaction.vout.push(
new CTxOut({
script: bytesToHex([OP['RETURN'], 32, ...hexToBytes(hash)]),
value,
})
);
this.#addScript({
script: [OP['RETURN'], 32, ...hexToBytes(hash)],
value,
});
return this;
}

Expand Down
44 changes: 44 additions & 0 deletions tests/unit/encoding.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import {
varIntToNum,
} from '../../scripts/encoding.js';
import { describe, it, test, expect } from 'vitest';
import {
isColdAddress,
isExchangeAddress,
isShieldAddress,
isStandardAddress,
isValidPIVXAddress,
} from '../../scripts/misc';

describe('parse WIF tests', () => {
it('Parses WIF correctly', () => {
Expand Down Expand Up @@ -109,3 +116,40 @@ describe('num to bytes tests', () => {
});
});
});

describe('Address validation', () => {
const addresses = [
{ addr: 'DSxfioagfTXeCX1teMQNbWnzqWkz5mZNtH', desc: 'p2pkh' },
{ addr: 'EXMDbnWT4K3nWfK1311otFrnYLcFSipp3iez', desc: 'exc' },
{ addr: 'SbqnpKgFRm1zPLHQRRuvuUH4Tyc6Em53xt', desc: 'cold' },
{
addr: 'ps10g8s4f87fc787e8nzw65men80kqsdmem4uu3yj6zer3uz7ya4m2fjnvc9l5f3009ur6kszfjp34',
desc: 'shield',
},
{ addr: 'DSxfioagfTUeCX1teMQNbWnzqWkz5mZNtH', desc: 'invalid' },
{ addr: 'EXMDbnWT4K3nWfK2311otFrnYLcFSipp3iez', desc: 'invalid' },
{ addr: 'SbqnpKgFRm1zPLHQRRuvuUh4Tyc6Em53xt', desc: 'invalid' },
{
addr: 'ps10g8s4f87fc78788nzw65men80kqsdmem4uu3yj6zer3uz7ya4m2fjnvc9l5f3009ur6kszfjp34',
desc: 'invalid',
},
];

it.each(addresses)('recognises shield addresses', (address) => {
expect(isShieldAddress(address.addr)).toBe(address.desc === 'shield');
});
it.each(addresses)('recognises p2pkh addresses', (address) => {
expect(isStandardAddress(address.addr)).toBe(address.desc === 'p2pkh');
});
it.each(addresses)('recognises exchange addresses', (address) => {
expect(isExchangeAddress(address.addr)).toBe(address.desc === 'exc');
});
it.each(addresses)('recognises cold addresses', (address) => {
expect(isColdAddress(address.addr)).toBe(address.desc === 'cold');
});
it.each(addresses)('recognises valid addresses', (address) => {
expect(isValidPIVXAddress(address.addr)).toBe(
address.desc !== 'invalid'
);
});
});
Loading

0 comments on commit 84eb64d

Please sign in to comment.