Skip to content
Permalink
Browse files

Always use byte-encoding for Shelley addresses (#1183)

* convert accounting tx

* fix utxo transactions

* fix transfer

* fix send screen

* eslint fix
  • Loading branch information
SebastienGllmt committed Dec 3, 2019
1 parent 97326d7 commit 264917f95062e56efd2c1b0270820b036c98b09c
@@ -912,7 +912,9 @@ export default class AdaApi {
if (shouldSendAll) {
unsignedTxResponse = environment.isShelley()
? shelleySendAllUnsignedTx(
receiver,
Buffer.from(
RustModule.WalletV3.Address.from_string(receiver).as_bytes()
).toString('hex'),
addressedUtxo
)
: byronSendAllUnsignedTx(
@@ -927,7 +929,9 @@ export default class AdaApi {
const changeAddr = nextUnusedInternal.addressInfo;
unsignedTxResponse = environment.isShelley()
? shelleyNewAdaUnsignedTx(
receiver,
Buffer.from(
RustModule.WalletV3.Address.from_string(receiver).as_bytes()
).toString('hex'),
amount,
[{
address: changeAddr.addr.Hash,
@@ -2,6 +2,7 @@

import type { CoreAddressT } from '../database/primitives/enums';
import { CoreAddressTypes } from '../database/primitives/enums';
import { Bech32Prefix } from '../../../../../config/stringConfig';
import { RustModule } from '../../cardanoCrypto/rustLoader';

export function addressToKind(
@@ -69,3 +70,23 @@ export function verifyAddress(
}
}
}

export function addressToDisplayString(
address: string
): string {
try {
// Need to try parsing as a legacy address first
// Since parsing as bech32 directly may give a wrong result if the address contains a 1
RustModule.WalletV2.Address.from_base58(address);
return address;
} catch (_e1) {
try {
const wasmAddr = RustModule.WalletV3.Address.from_bytes(
Buffer.from(address, 'hex')
);
return wasmAddr.to_string(Bech32Prefix.ADDRESS);
} catch (_e2) {
throw new Error('addressToKind failed to parse address type ' + address);
}
}
}
@@ -27,7 +27,9 @@ describe('Create unsigned TX for account', () => {

const unsignedTxResponse = buildUnsignedAccountTx(
senderKey.to_public(),
'ca1qw8mq0p65pf028qgd32t6szeatfd9epx4jyl5jeuuswtlkyqpdguqeh83d4',
Buffer.from(RustModule.WalletV3.Address.from_string(
'ca1qw8mq0p65pf028qgd32t6szeatfd9epx4jyl5jeuuswtlkyqpdguqeh83d4'
).as_bytes()).toString('hex'),
{
amount: new BigNumber(2000000)
},
@@ -64,7 +66,9 @@ describe('Create unsigned TX for account', () => {

expect(() => buildUnsignedAccountTx(
senderKey.to_public(),
'ca1qw8mq0p65pf028qgd32t6szeatfd9epx4jyl5jeuuswtlkyqpdguqeh83d4',
Buffer.from(RustModule.WalletV3.Address.from_string(
'ca1qw8mq0p65pf028qgd32t6szeatfd9epx4jyl5jeuuswtlkyqpdguqeh83d4'
).as_bytes()).toString('hex'),
{
amount: new BigNumber(2000000),
},
@@ -79,7 +83,9 @@ describe('Create unsigned TX for account', () => {

expect(() => buildUnsignedAccountTx(
senderKey.to_public(),
'ca1qw8mq0p65pf028qgd32t6szeatfd9epx4jyl5jeuuswtlkyqpdguqeh83d4',
Buffer.from(RustModule.WalletV3.Address.from_string(
'ca1qw8mq0p65pf028qgd32t6szeatfd9epx4jyl5jeuuswtlkyqpdguqeh83d4'
).as_bytes()).toString('hex'),
{
amount: new BigNumber(2000000),
},
@@ -25,6 +25,9 @@ export function buildUnsignedAccountTx(
typeSpecific: SendType,
accountBalance: BigNumber,
): RustModule.WalletV3.InputOutput {
const wasmReceiver = RustModule.WalletV3.Address.from_bytes(
Buffer.from(receiver, 'hex')
);
if (typeSpecific.amount != null && typeSpecific.amount.gt(accountBalance)) {
throw new NotEnoughMoneyToSendError();
}
@@ -52,7 +55,7 @@ export function buildUnsignedAccountTx(

if (typeSpecific.amount != null) {
fakeTxBuilder.add_output(
RustModule.WalletV3.Address.from_string(receiver),
wasmReceiver,
// the value we put in here is irrelevant. Just need some value to be able to calculate fee
RustModule.WalletV3.Value.from_str('1')
);
@@ -79,7 +82,7 @@ export function buildUnsignedAccountTx(
if (typeSpecific.amount != null) {
const value = RustModule.WalletV3.Value.from_str(typeSpecific.amount.toString());
ioBuilder.add_output(
RustModule.WalletV3.Address.from_string(receiver),
wasmReceiver,
value
);
}
@@ -9,6 +9,7 @@ import {
stringifyError,
} from '../../../../utils/logging';
import { LOVELACES_PER_ADA } from '../../../../config/numbersConfig';
import { Bech32Prefix } from '../../../../config/stringConfig';
import {
GenerateTransferTxError
} from '../../errors';
@@ -70,8 +71,11 @@ export async function buildDaedalusTransferTx(payload: {|
fee: fee.dividedBy(LOVELACES_PER_ADA),
id: Buffer.from(fragment.id().as_bytes()).toString('hex'),
encodedTx: fragment.as_bytes(),
// recall: Daedalus addresses all have to be legacy so we don't turn them to bech32
senders: Object.keys(addressKeys),
receiver: outputAddr,
receiver: RustModule.WalletV3.Address.from_bytes(
Buffer.from(outputAddr, 'hex')
).to_string(Bech32Prefix.ADDRESS)
};
} catch (error) {
Logger.error(`daedalusTransfer::buildTransferTx ${stringifyError(error)}`);
@@ -91,7 +91,9 @@ export function sendAllUnsignedTxFromUtxo(
fakeIOBuilder.add_input(input);
}
fakeIOBuilder.add_output(
RustModule.WalletV3.Address.from_string(receiver),
RustModule.WalletV3.Address.from_bytes(
Buffer.from(receiver, 'hex')
),
RustModule.WalletV3.Value.from_str(totalBalance.toString())
);
const feeValue = fakeIOBuilder.estimate_fee(
@@ -170,7 +172,9 @@ export function newAdaUnsignedTxFromUtxo(

const ioBuilder = RustModule.WalletV3.InputOutputBuilder.empty();
ioBuilder.add_output(
RustModule.WalletV3.Address.from_string(receiver),
RustModule.WalletV3.Address.from_bytes(
Buffer.from(receiver, 'hex')
),
RustModule.WalletV3.Value.from_str(amount)
);
const payload = certificate != null
@@ -198,7 +202,9 @@ export function newAdaUnsignedTxFromUtxo(
payload,
feeAlgorithm,
RustModule.WalletV3.OutputPolicy.one(
RustModule.WalletV3.Address.from_string(changeAddr.address)
RustModule.WalletV3.Address.from_bytes(
Buffer.from(changeAddr.address, 'hex')
)
)
);
const addedChange = filterToUsedChange(
@@ -264,7 +270,9 @@ function filterToUsedChange(
const possibleDuplicates = selectedUtxos.filter(utxo => utxo.receiver === changeAddr.address);

const change = [];
const changeAddrWasm = RustModule.WalletV3.Address.from_string(changeAddr.address);
const changeAddrWasm = RustModule.WalletV3.Address.from_bytes(
Buffer.from(changeAddr.address, 'hex')
);
const changeAddrPayload = Buffer.from(changeAddrWasm.as_bytes()).toString('hex');
for (let i = 0; i < outputs.size(); i++) {
const output = outputs.get(i);
@@ -43,17 +43,20 @@ import {
const keys = [
{
legacyAddress: 'Ae2tdPwUPEZKX8N2TjzBXLy5qrecnQUniTd2yxE8mWyrh2djNpUkbAtXtP4',
bechAddress: 'ca1qw8mq0p65pf028qgd32t6szeatfd9epx4jyl5jeuuswtlkyqpdguqeh83d4',
// ca1qw8mq0p65pf028qgd32t6szeatfd9epx4jyl5jeuuswtlkyqpdguqeh83d4
bechAddress: '038fb03c3aa052f51c086c54bd4059ead2d2e426ac89fa4b3ce41cbfd8800b51c0',
pubKey: '8fb03c3aa052f51c086c54bd4059ead2d2e426ac89fa4b3ce41cbfd8800b51c02623fceb96b07408531a5cb259f53845a38d6b68928e7c0c7e390f07545d0e62',
},
{
legacyAddress: 'Ae2tdPwUPEZ4xAL3nxLq4Py7BfS1D2tJ3u2rxZGnrAXC8TNkWhTaz41J3FN',
bechAddress: 'ca1q0j6cetm7zqsagm5zz5fmav9jg37n4cferj23h370kptrpfj095fxcy43lj',
// ca1q0j6cetm7zqsagm5zz5fmav9jg37n4cferj23h370kptrpfj095fxcy43lj
bechAddress: '03e5ac657bf0810ea37410a89df5859223e9d709c8e4a8de3e7d82b18532796893',
pubKey: 'e5ac657bf0810ea37410a89df5859223e9d709c8e4a8de3e7d82b185327968939a254def91bb75e94bda9c605f7f87481082742e1e51d8858965c9a40491fc94',
},
{
legacyAddress: 'Ae2tdPwUPEZEtwz7LKtJn9ub8y7ireuj3sq2yUCZ57ccj6ZkJKn7xEiApV9',
bechAddress: 'ca1q0ewtxsk489t9g7vs64prkm0hfvz6aemtvtv57rkfwmxyp3yhtxtwhtm3gd',
// ca1q0ewtxsk489t9g7vs64prkm0hfvz6aemtvtv57rkfwmxyp3yhtxtwhtm3gd
bechAddress: '03f2e59a16a9cab2a3cc86aa11db6fba582d773b5b16ca78764bb6620624baccb7',
pubKey: 'f2e59a16a9cab2a3cc86aa11db6fba582d773b5b16ca78764bb6620624baccb7c03adf6448459f2b8d5c32033a160de8b5412d1952794190c4fc6b4716a8b8eb',
}
];
@@ -84,7 +87,8 @@ const sampleUtxos: Array<RemoteUnspentOutput> = [

const sampleAdaAddresses: Array<{| ...Address, ...Addressing |}> = [
{
address: 'ca1q0ewtxsk489t9g7vs64prkm0hfvz6aemtvtv57rkfwmxyp3yhtxtwhtm3gd',
// ca1q0ewtxsk489t9g7vs64prkm0hfvz6aemtvtv57rkfwmxyp3yhtxtwhtm3gd
address: '03f2e59a16a9cab2a3cc86aa11db6fba582d773b5b16ca78764bb6620624baccb7',
addressing: {
path: [1, 11],
startLevel: Bip44DerivationLevels.CHAIN.level,
@@ -328,18 +332,24 @@ describe('Create signed transactions', () => {

it('Transaction with a certificate is also valid', () => {
const unsignedTxResponse = newAdaUnsignedTx(
'ca1sw8mq0p65pf028qgd32t6szeatfd9epx4jyl5jeuuswtlkyqpdguq9rance',
Buffer.from(RustModule.WalletV3.Address.from_string(
'ca1sw8mq0p65pf028qgd32t6szeatfd9epx4jyl5jeuuswtlkyqpdguq9rance'
).as_bytes()).toString('hex'),
'5000', // smaller than input
[{
address: 'addr1s5quq8utjkrfntnkngjxa9u9mdd8pcprjal2fwzkm7k0y0prx3k276qm0j8',
address: Buffer.from(RustModule.WalletV3.Address.from_string(
'addr1s5quq8utjkrfntnkngjxa9u9mdd8pcprjal2fwzkm7k0y0prx3k276qm0j8'
).as_bytes()).toString('hex'),
addressing: {
path: [1, 0],
startLevel: Bip44DerivationLevels.CHAIN.level,
},
}],
[{
amount: '2000000',
receiver: 'ca1ssuvzjs82mshgvyp4r4lmwgknvgjswnm7mpcq3wycjj7v2nk393e6qwqr79etp5e4emf5frwj7zakknsuq3ewl4yhptdlt8j8s3ngm906x2vwl',
receiver: Buffer.from(RustModule.WalletV3.Address.from_string(
'ca1ssuvzjs82mshgvyp4r4lmwgknvgjswnm7mpcq3wycjj7v2nk393e6qwqr79etp5e4emf5frwj7zakknsuq3ewl4yhptdlt8j8s3ngm906x2vwl'
).as_bytes()).toString('hex'),
tx_hash: '86e36b6a65d82c9dcc0370b0ee3953aee579db0b837753306405c28a74de5550',
tx_index: 0,
utxo_id: '86e36b6a65d82c9dcc0370b0ee3953aee579db0b837753306405c28a74de55500',
@@ -7,6 +7,8 @@ import {
stringifyError,
} from '../../../../utils/logging';
import { LOVELACES_PER_ADA } from '../../../../config/numbersConfig';
import { Bech32Prefix } from '../../../../config/stringConfig';
import { addressToDisplayString } from '../../lib/storage/bridge/utils';
import {
GenerateTransferTxError
} from '../../errors';
@@ -54,15 +56,19 @@ export async function buildYoroiTransferTx(payload: {|
true,
);

const uniqueSenders = Array.from(new Set(senderUtxos.map(utxo => utxo.receiver)));

// return summary of transaction
return {
recoveredBalance: totalBalance.dividedBy(LOVELACES_PER_ADA),
fee: fee.dividedBy(LOVELACES_PER_ADA),
id: Buffer.from(fragment.id().as_bytes()).toString('hex'),
encodedTx: fragment.as_bytes(),
// only display unique addresses
senders: Array.from(new Set(senderUtxos.map(utxo => utxo.receiver))),
receiver: outputAddr,
// recall: some addresses may be legacy, some may be Shelley
senders: uniqueSenders.map(addr => addressToDisplayString(addr)),
receiver: RustModule.WalletV3.Address.from_bytes(
Buffer.from(outputAddr, 'hex')
).to_string(Bech32Prefix.ADDRESS)
};
} catch (error) {
Logger.error(`transfer::buildTransferTx ${stringifyError(error)}`);
@@ -196,9 +196,10 @@ describe('Shelley era tx format tests', () => {
const txId = '915f2e6865fb31cc93410efb6c0e580ca74862374b3da461e20135c01f312e7c';
const inputAmount = '1000000';
const txIndex = 0;
const outAddress = RustModule.WalletV3.Address.from_bytes(
Buffer.from('038e2840fed90d2138761d8a14a4cbed08ed00cf908b07f94ec5fa9db6f4d7e74f', 'hex')
).to_string('addr');
const bech32Addr = 'addr1qw8zss87myxjzwrkrk9pffxta5yw6qx0jz9s072wchafmdh56ln5704fx9z';
const outAddress = Buffer.from(RustModule.WalletV3.Address.from_string(
bech32Addr
).as_bytes()).toString('hex');

const daedalusWallet = getCryptoDaedalusWalletFromMnemonics(words);
const checker = RustModule.WalletV2.DaedalusAddressChecker.new(daedalusWallet);
@@ -225,7 +226,7 @@ describe('Shelley era tx format tests', () => {
expect(transferInfo.fee.toString()).toBe('0.155383');
expect(transferInfo.recoveredBalance.toString()).toBe('1');
expect(transferInfo.senders).toEqual([address]);
expect(transferInfo.receiver).toBe(outAddress);
expect(transferInfo.receiver).toBe(bech32Addr);

// check tx itself
const fragment = RustModule.WalletV3.Fragment.from_bytes(transferInfo.encodedTx);
@@ -241,7 +242,7 @@ describe('Shelley era tx format tests', () => {
const outputs = signedTx.outputs();
expect(outputs.size()).toEqual(1);
const output = outputs.get(0);
expect(output.address().to_string('addr')).toEqual(outAddress);
expect(output.address().to_string('addr')).toEqual(bech32Addr);
expect(output.value().to_str()).toEqual('844617');

const witnesses = signedTx.witnesses();
@@ -257,9 +258,7 @@ describe('Shelley era tx format tests', () => {
const txId = '915f2e6865fb31cc93410efb6c0e580ca74862374b3da461e20135c01f312e7c';
const inputAmount = '1000';
const txIndex = 0;
const outAddress = RustModule.WalletV3.Address.from_bytes(
Buffer.from('038e2840fed90d2138761d8a14a4cbed08ed00cf908b07f94ec5fa9db6f4d7e74f', 'hex')
).to_string('addr');
const outAddress = '038e2840fed90d2138761d8a14a4cbed08ed00cf908b07f94ec5fa9db6f4d7e74f';

const daedalusWallet = getCryptoDaedalusWalletFromMnemonics(words);
const checker = RustModule.WalletV2.DaedalusAddressChecker.new(daedalusWallet);
@@ -290,9 +289,10 @@ describe('Shelley era tx format tests', () => {
const txId = '915f2e6865fb31cc93410efb6c0e580ca74862374b3da461e20135c01f312e7c';
const inputAmount = '1000001';
const txIndex = 0;
const outAddress = RustModule.WalletV3.Address.from_bytes(
Buffer.from('038e2840fed90d2138761d8a14a4cbed08ed00cf908b07f94ec5fa9db6f4d7e74f', 'hex')
).to_string('addr');
const bech32Addr = 'addr1qw8zss87myxjzwrkrk9pffxta5yw6qx0jz9s072wchafmdh56ln5704fx9z';
const outAddress = Buffer.from(RustModule.WalletV3.Address.from_string(
bech32Addr
).as_bytes()).toString('hex');

const daedalusWallet = getCryptoDaedalusWalletFromMnemonics(words);
const checker = RustModule.WalletV2.DaedalusAddressChecker.new(daedalusWallet);
@@ -323,7 +323,7 @@ describe('Shelley era tx format tests', () => {
expect(transferInfo.fee.toString()).toBe('0.155482');
expect(transferInfo.recoveredBalance.toString()).toBe('100.0001');
expect(transferInfo.senders).toEqual([address]);
expect(transferInfo.receiver).toBe(outAddress);
expect(transferInfo.receiver).toBe(bech32Addr);

// check tx itself
const fragment = RustModule.WalletV3.Fragment.from_bytes(transferInfo.encodedTx);
@@ -339,7 +339,7 @@ describe('Shelley era tx format tests', () => {
const outputs = signedTx.outputs();
expect(outputs.size()).toEqual(1);
const output = outputs.get(0);
expect(output.address().to_string('addr')).toEqual(outAddress);
expect(output.address().to_string('addr')).toEqual(bech32Addr);
expect(output.value().to_str()).toEqual('99844618');

const witnesses = signedTx.witnesses();

0 comments on commit 264917f

Please sign in to comment.
You can’t perform that action at this time.