PHP WebShell
Текущая директория: /opt/BitGoJS/modules/sdk-coin-xlm/test/unit
Просмотр файла: xlm.ts
import * as should from 'should';
import { randomBytes } from 'crypto';
import * as stellar from 'stellar-sdk';
import { Environments, Wallet } from '@bitgo/sdk-core';
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
import { BitGoAPI } from '@bitgo/sdk-api';
import { Txlm } from '../../src';
import { KeyPair } from '../../src/lib/keyPair';
import nock from 'nock';
import * as assert from 'assert';
nock.enableNetConnect();
describe('XLM:', function () {
let bitgo: TestBitGoAPI;
let basecoin;
let uri;
before(function () {
bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' });
bitgo.safeRegister('txlm', Txlm.createInstance);
bitgo.initializeTestVars();
basecoin = bitgo.coin('txlm');
uri = Environments[bitgo.getEnv()].uri;
});
after(function () {
nock.cleanAll();
});
describe('Addresses:', () => {
const noMemoIdAddress = 'GBIEJQUARJ33DIZU4AIRDOKYPSVK66Z3O5XU7OOI7LUOAJWTPI4OA4JI';
const validMemoIdAddress = 'GBIEJQUARJ33DIZU4AIRDOKYPSVK66Z3O5XU7OOI7LUOAJWTPI4OA4JI?memoId=5';
const invalidMemoIdAddress = 'GBIEJQUARJ33DIZU4AIRDOKYPSVK66Z3O5XU7OOI7LUOAJWTPI4OA4JI?memoId=x';
const multipleMemoIdAddress = 'GBIEJQUARJ33DIZU4AIRDOKYPSVK66Z3O5XU7OOI7LUOAJWTPI4OA4JI?memoId=5&memoId=3';
// Muxed address of GAFHNUKOZT6QA4WJS6YSDCN6XETEOP7Q6AOHFFLUGLNVM6FY724ULORC
const validMuxedAddress = 'MAFHNUKOZT6QA4WJS6YSDCN6XETEOP7Q6AOHFFLUGLNVM6FY724UKAAAAAAAAAAAAEJCO';
const validMuxedBaseAddress = 'GAFHNUKOZT6QA4WJS6YSDCN6XETEOP7Q6AOHFFLUGLNVM6FY724ULORC';
it('should get address details without memoId', function () {
const addressDetails = basecoin.getAddressDetails(noMemoIdAddress);
addressDetails.address.should.equal(noMemoIdAddress);
should.not.exist(addressDetails.memoId);
});
it('should get address details with memoId', function () {
const addressDetails = basecoin.getAddressDetails(validMemoIdAddress);
addressDetails.address.should.equal(validMemoIdAddress.split('?')[0]);
addressDetails.memoId.should.equal('5');
});
it('should throw on invalid memo id address', () => {
(() => {
basecoin.getAddressDetails(invalidMemoIdAddress);
}).should.throw();
});
it('should throw on multiple memo id address', () => {
(() => {
basecoin.getAddressDetails(multipleMemoIdAddress);
}).should.throw();
});
it('should validate address', function () {
basecoin.isValidAddress('GBRIS6W5OZNWWFJA6GYRF3JBK5WZNX5WWD2KC6NCOOIEMF7H6JMQLUI4').should.equal(true);
basecoin.isValidAddress('GDU2FEL6THGGOFDHHP4I5FHNWY4S2SXYUBCEDB5ZREMD6UFRT4SYWSW2').should.equal(true);
basecoin.isValidAddress('GDU2FEL6THGGOFDHHP4I5FHNWY4S2SXYUBCEDB5ZREMD6UFRT4SYWSW2?memoId=1').should.equal(true);
basecoin.isValidAddress('GDU2FEL6THGGOFDHHP4I5FHNWY4S2SXYUBCEDB5ZREMD6UFRT4SYWSW2?memoId=x').should.equal(false);
basecoin.isValidAddress('SBKGCMBY56MHTT4EGE3YJIYL4CPWKSGJ7VDEQF4J3B3YO576KNL7DOYJ').should.equal(false); // private key
basecoin.isValidAddress('r2udSsspYjWSoUZxzxLzV6RxGcbygngJ8').should.equal(false); // xrp account
});
it('should validate muxed address', function () {
basecoin.isValidAddress(validMuxedAddress).should.equal(true);
const muxedAddressDetails = basecoin.getAddressDetails(validMuxedAddress);
muxedAddressDetails.should.deepEqual({
baseAddress: validMuxedBaseAddress,
address: validMuxedAddress,
id: '1',
memoId: undefined,
});
});
it('verifyAddress should work', async function () {
await basecoin.verifyAddress({
address: 'GBRIS6W5OZNWWFJA6GYRF3JBK5WZNX5WWD2KC6NCOOIEMF7H6JMQLUI4',
rootAddress: 'GBRIS6W5OZNWWFJA6GYRF3JBK5WZNX5WWD2KC6NCOOIEMF7H6JMQLUI4',
});
await basecoin.verifyAddress({
address: 'GDU2FEL6THGGOFDHHP4I5FHNWY4S2SXYUBCEDB5ZREMD6UFRT4SYWSW2?memoId=1',
rootAddress: 'GDU2FEL6THGGOFDHHP4I5FHNWY4S2SXYUBCEDB5ZREMD6UFRT4SYWSW2',
});
await basecoin.verifyAddress({
address: validMuxedAddress,
rootAddress: validMuxedBaseAddress,
});
assert.rejects(
basecoin.verifyAddress({
address: 'GDU2FEL6THGGOFDHHP4I5FHNWY4S2SXYUBCEDB5ZREMD6UFRT4SYWSW2?memoId=243432',
rootAddress: 'GBRIS6W5OZNWWFJA6GYRF3JBK5WZNX5WWD2KC6NCOOIEMF7H6JMQLUI4',
})
);
assert.rejects(
basecoin.verifyAddress({
address: 'GDU2FEL6THGGOFDHHP4I5FHNWY4S2SXYUBCEDB5ZREMD6UFRT4SYWSW2=x',
rootAddress: 'GDU2FEL6THGGOFDHHP4I5FHNWY4S2SXYUBCEDB5ZREMD6UFRT4SYWSW2',
})
);
assert.rejects(
basecoin.verifyAddress({
address: 'SBKGCMBY56MHTT4EGE3YJIYL4CPWKSGJ7VDEQF4J3B3YO576KNL7DOYJ',
})
);
assert.rejects(
basecoin.verifyAddress({
address: 'r2udSsspYjWSoUZxzxLzV6RxGcbygngJ8',
})
);
});
});
it('should validate pub key', () => {
const { pub } = basecoin.keychains().create();
basecoin.isValidPub(pub).should.equal(true);
});
it('should validate root keypair', () => {
const { pub, prv } = basecoin.keychains().create({ isRootKey: true });
basecoin.isValidPub(pub).should.equal(true);
basecoin.isValidPrv(prv).should.equal(true);
});
it('should validate stellar username', function () {
basecoin.isValidStellarUsername('foo@bar.baz').should.equal(true);
basecoin.isValidStellarUsername('foo_bar9.baz').should.equal(true);
basecoin.isValidStellarUsername('foo+bar_9.baz').should.equal(true);
basecoin.isValidStellarUsername('').should.equal(false);
basecoin.isValidStellarUsername('foo bar.baz').should.equal(false); // whitespace is not allowed
basecoin.isValidStellarUsername('Foo@bar.baz').should.equal(false); // only lowercase letters are allowed
});
it('Should explain an XLM transaction', async function () {
const signedExplanation = await basecoin.explainTransaction({
txBase64:
'AAAAAMDHAbd3O7B2auR1e+EH/LRKe8IcQBOF+XP2lOxWi1PfAAAB9AAEvJEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAAB1RFU1RJTkcAAAAAAQAAAAAAAAABAAAAALgEl4p84728zfXtl/JdOsx3QbI97mcybqcXdfgdv54zAAAAAAAAAAEqBfIAAAAAAAAAAAFWi1PfAAAAQDoqo7juOBZawMlk8znIbYqSKemjgmINosp/P4+0SFGo/xJy1YgD6YEc65aWuyBxucFFBXCSlAxP2Z7nPMyjewM=',
});
signedExplanation.outputAmount.should.equal('5000000000');
signedExplanation.outputAmounts.should.have.property('txlm', '5000000000');
signedExplanation.outputs.length.should.equal(1);
signedExplanation.outputs[0].address.should.equal('GC4AJF4KPTR33PGN6XWZP4S5HLGHOQNSHXXGOMTOU4LXL6A5X6PDH445');
signedExplanation.outputs[0].amount.should.equal('5000000000');
signedExplanation.outputs[0].coin.should.equal('txlm');
signedExplanation.fee.fee.should.equal('500');
signedExplanation.memo.value.should.equal('TESTING');
signedExplanation.memo.type.should.equal('text');
signedExplanation.changeOutputs.length.should.equal(0);
signedExplanation.changeAmount.should.equal('0');
const unsignedExplanation = await basecoin.explainTransaction({
txBase64:
'AAAAAMDHAbd3O7B2auR1e+EH/LRKe8IcQBOF+XP2lOxWi1PfAAAAZAAEvJEAAAACAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAEAAAABAAAAAAAAAAEAAAAAuASXinzjvbzN9e2X8l06zHdBsj3uZzJupxd1+B2/njMAAAAAAAAAAlQL5AAAAAAAAAAAAA==',
});
unsignedExplanation.outputAmount.should.equal('10000000000');
unsignedExplanation.outputAmounts.should.have.property('txlm', '10000000000');
unsignedExplanation.outputs.length.should.equal(1);
unsignedExplanation.outputs[0].address.should.equal('GC4AJF4KPTR33PGN6XWZP4S5HLGHOQNSHXXGOMTOU4LXL6A5X6PDH445');
unsignedExplanation.outputs[0].amount.should.equal('10000000000');
unsignedExplanation.outputs[0].coin.should.equal('txlm');
unsignedExplanation.fee.fee.should.equal('100');
unsignedExplanation.memo.value.should.equal('1');
unsignedExplanation.memo.type.should.equal('id');
unsignedExplanation.changeOutputs.length.should.equal(0);
unsignedExplanation.changeAmount.should.equal('0');
});
it('Should explain an XLM transaction', async function () {
const signedExplanation = await basecoin.explainTransaction({
txBase64:
'AAAAAMDHAbd3O7B2auR1e+EH/LRKe8IcQBOF+XP2lOxWi1PfAAAB9AAEvJEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAAB1RFU1RJTkcAAAAAAQAAAAAAAAABAAAAALgEl4p84728zfXtl/JdOsx3QbI97mcybqcXdfgdv54zAAAAAAAAAAEqBfIAAAAAAAAAAAFWi1PfAAAAQDoqo7juOBZawMlk8znIbYqSKemjgmINosp/P4+0SFGo/xJy1YgD6YEc65aWuyBxucFFBXCSlAxP2Z7nPMyjewM=',
});
signedExplanation.outputAmount.should.equal('5000000000');
signedExplanation.outputAmounts.should.have.property('txlm', '5000000000');
signedExplanation.outputs.length.should.equal(1);
signedExplanation.outputs[0].address.should.equal('GC4AJF4KPTR33PGN6XWZP4S5HLGHOQNSHXXGOMTOU4LXL6A5X6PDH445');
signedExplanation.outputs[0].amount.should.equal('5000000000');
signedExplanation.outputs[0].coin.should.equal('txlm');
signedExplanation.fee.fee.should.equal('500');
signedExplanation.memo.value.should.equal('TESTING');
signedExplanation.memo.type.should.equal('text');
signedExplanation.changeOutputs.length.should.equal(0);
signedExplanation.changeAmount.should.equal('0');
const unsignedExplanation = await basecoin.explainTransaction({
txBase64:
'AAAAAMDHAbd3O7B2auR1e+EH/LRKe8IcQBOF+XP2lOxWi1PfAAAAZAAEvJEAAAACAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAEAAAABAAAAAAAAAAEAAAAAuASXinzjvbzN9e2X8l06zHdBsj3uZzJupxd1+B2/njMAAAAAAAAAAlQL5AAAAAAAAAAAAA==',
});
unsignedExplanation.outputAmount.should.equal('10000000000');
unsignedExplanation.outputAmounts.should.have.property('txlm', '10000000000');
unsignedExplanation.outputs.length.should.equal(1);
unsignedExplanation.outputs[0].address.should.equal('GC4AJF4KPTR33PGN6XWZP4S5HLGHOQNSHXXGOMTOU4LXL6A5X6PDH445');
unsignedExplanation.outputs[0].amount.should.equal('10000000000');
unsignedExplanation.outputs[0].coin.should.equal('txlm');
unsignedExplanation.fee.fee.should.equal('100');
unsignedExplanation.memo.value.should.equal('1');
unsignedExplanation.memo.type.should.equal('id');
unsignedExplanation.changeOutputs.length.should.equal(0);
unsignedExplanation.changeAmount.should.equal('0');
});
it('Should explain an XLM transaction by passing in a hex format', async function () {
const signedExplanation = await basecoin.explainTransaction({
txHex:
'0000000200000000aa0c5c593ed36af12269dc4605dd34da32fdab5c676fa0644f28e598dd57512a0000afc800000000009896810000000100000000000000000000000000000000000000000000000100000000000000010000010000000000000000010a76d14eccfd0072c997b12189beb926473ff0f01c72957432db5678b8feb945000000000000000005f5e1000000000000000000',
});
signedExplanation.outputAmount.should.equal('100000000');
signedExplanation.outputAmounts.should.have.property('txlm', '100000000');
signedExplanation.outputs.length.should.equal(1);
signedExplanation.outputs[0].address.should.equal(
'MAFHNUKOZT6QA4WJS6YSDCN6XETEOP7Q6AOHFFLUGLNVM6FY724UKAAAAAAAAAAAAEJCO'
);
signedExplanation.outputs[0].amount.should.equal('100000000');
signedExplanation.outputs[0].coin.should.equal('txlm');
signedExplanation.fee.fee.should.equal('45000');
signedExplanation.changeOutputs.length.should.equal(0);
signedExplanation.changeAmount.should.equal('0');
});
it('Should explain a trustline transaction', async function () {
const explanation = await basecoin.explainTransaction({
txBase64:
'AAAAAIKWO6R0/V4oJDk2LZsdiEInIzgJ6L0GxmSU2Ffs8Y7ZAAABLAAIj4EAAAACAAAAAAAAAAAAAAABAAAAAAAAAAYAAAABQlNUAAAAAABhNDpbuY4frrgwVQqkws7jxK+k4IMrJ6BaE0OFUva9vwAAAOjUpRAAAAAAAAAAAAA=',
});
explanation.outputAmount.should.equal('0');
explanation.fee.fee.should.equal('300');
explanation.memo.should.be.empty();
explanation.changeOutputs.length.should.equal(0);
explanation.changeAmount.should.equal('0');
explanation.operations.length.should.equal(1);
explanation.operations[0].limit.should.equal('1000000000000');
explanation.operations[0].coin.should.equal('txlm:BST-GBQTIOS3XGHB7LVYGBKQVJGCZ3R4JL5E4CBSWJ5ALIJUHBKS6263644L');
explanation.operations[0].type.should.equal('changeTrust');
explanation.operations[0].should.have.property('asset');
explanation.operations[0].asset.code.should.equal('BST');
explanation.operations[0].asset.issuer.should.equal('GBQTIOS3XGHB7LVYGBKQVJGCZ3R4JL5E4CBSWJ5ALIJUHBKS6263644L');
});
it('Should explain a token transaction', async function () {
const explanation = await basecoin.explainTransaction({
txBase64:
'AAAAAIXpiGPR/Yc+gSN614hAf1N1hecXFL7Lac99olpq38K/AAAAZAAC9TAAAAAEAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAgpY7pHT9XigkOTYtmx2IQicjOAnovQbGZJTYV+zxjtkAAAABQlNUAAAAAABhNDpbuY4frrgwVQqkws7jxK+k4IMrJ6BaE0OFUva9vwAAAAAdzWUAAAAAAAAAAAFq38K/AAAAQPJTLIGGY06BuVDw0ISasYwHZpR6V38CaOfGhSooclY+4IBE9JKdKuMyGNXXCcFxM/NxrX64jhBXk+lWvjjo4wY=',
});
explanation.outputAmount.should.equal('0');
explanation.fee.fee.should.equal('100');
explanation.memo.should.be.empty();
explanation.changeOutputs.length.should.equal(0);
explanation.changeAmount.should.equal('0');
explanation.outputAmounts.should.have.property(
'txlm:BST-GBQTIOS3XGHB7LVYGBKQVJGCZ3R4JL5E4CBSWJ5ALIJUHBKS6263644L',
'500000000'
);
});
it('isValidMemoId should work', function () {
basecoin.isValidMemo({ value: '1', type: 'id' }).should.equal(true);
basecoin.isValidMemo({ value: 'uno', type: 'text' }).should.equal(true);
const buffer = Buffer.alloc(32).fill(10);
basecoin.isValidMemo({ value: buffer, type: 'hash' }).should.equal(true);
basecoin.isValidMemo({ value: buffer.toString('hex'), type: 'hash' }).should.equal(true);
basecoin.isValidMemo({ value: 1, type: 'id' }).should.equal(false);
basecoin.isValidMemo({ value: 1, type: 'text' }).should.equal(false);
basecoin.isValidMemo({ value: '1', type: 'hash' }).should.equal(false);
basecoin.isValidMemo({ value: '1', type: 'return' }).should.equal(false);
});
it('should supplement wallet generation', async function () {
const walletParams = await basecoin.supplementGenerateWallet({});
walletParams.should.have.property('rootPrivateKey');
basecoin.isValidPrv(walletParams.rootPrivateKey).should.equal(true);
});
it('should supplement wallet generation with provided private key', async function () {
const rootPrivateKey = basecoin.generateKeyPair().prv;
const walletParams = await basecoin.supplementGenerateWallet({ rootPrivateKey });
walletParams.should.have.property('rootPrivateKey');
walletParams.rootPrivateKey.should.equal(rootPrivateKey);
});
describe('deriveKeyWithSeed', function () {
it('should derive key with seed', function () {
(() => {
basecoin.deriveKeyWithSeed('test');
}).should.throw('method deriveKeyWithSeed not supported for eddsa curve');
});
});
describe('Transaction Verification', function () {
let basecoin;
let wallet;
let halfSignedTransaction;
let rootKeyHalfSignedTransaction;
const userKeychain = {
pub: 'GA34NPQ4M54HHZBKSDZ5B3J3BZHTXKCZD4UFO2OYZERPOASK4DAATSIB',
prv: 'SDADJSTZNIKF46NM7LE3ZHMX4TJ2VJBL7PTERNDLWHZ5U6KNO5S7XFJD',
};
const backupKeychain = {
pub: 'GC3D3ZNNK7GHLMSWJA54DQO6QJUJJF7K6J5JGCEW45ZT6QMKZ6PMUHUM',
prv: 'SA22TDBINLZMGYUDVXGUP2JMYIQ3DTJE53PNQUVCDK73XRS6TDVYU7WW',
};
// This key pair is the decoded version of the userKeychain above
const rootKeychain = {
pub: '37c6be1c677873e42a90f3d0ed3b0e4f3ba8591f285769d8c922f7024ae0c009',
prv: 'c034ca796a145e79acfac9bc9d97e4d3aaa42bfbe648b46bb1f3da794d7765fb37c6be1c677873e42a90f3d0ed3b0e4f3ba8591f285769d8c922f7024ae0c009',
};
// This key pair is the decoded version of the backupKeychain above
const backupRootKeychain = {
pub: 'b63de5ad57cc75b256483bc1c1de82689497eaf27a930896e7733f418acf9eca',
prv: '35a98c286af2c36283adcd47e92cc221b1cd24eeded852a21abfbbc65e98eb8ab63de5ad57cc75b256483bc1c1de82689497eaf27a930896e7733f418acf9eca',
};
const prebuild = {
txBase64:
'AAAAAGRnXg19FteG/7zPd+jDC7LDvRlzgfFC+JrPhRep0kYiAAAAZAB/4cUAAAACAAAAAAAAAAAAAAABAAAAAQAAAABkZ14NfRbXhv+8z3fowwuyw70Zc4HxQviaz4UXqdJGIgAAAAEAAAAAmljT/+FedddnAHwo95dOC4RNy6eVLSehaJY34b9GxuYAAAAAAAAAAAehIAAAAAAAAAAAAA==',
txInfo: {
fee: 100,
sequence: '35995558267060226',
source: 'GBSGOXQNPULNPBX7XTHXP2GDBOZMHPIZOOA7CQXYTLHYKF5J2JDCF7LT',
operations: [
{
amount: '12.8', // 12.8 XLM
asset: { code: 'XLM' },
destination: 'GCNFRU774FPHLV3HAB6CR54XJYFYITOLU6KS2J5BNCLDPYN7I3DOMIPY',
type: 'payment',
},
],
signatures: [],
},
feeInfo: {
height: 123456,
xlmBaseFee: '100',
xlmBaseReserve: '5000000',
},
walletId: '5a78dd561c6258a907f1eeaee132f796',
};
const signedTxBase64 =
'AAAAAGRnXg19FteG/7zPd+jDC7LDvRlzgfFC+JrPhRep0kYiAAAAZAB/4cUAAAACAAAAAAAAAAAAAAABAAAAAQAAAABkZ14NfRbXhv+8z3fowwuyw70Zc4HxQviaz4UXqdJGIgAAAAEAAAAAmljT/+FedddnAHwo95dOC4RNy6eVLSehaJY34b9GxuYAAAAAAAAAAAehIAAAAAAAAAAAAUrgwAkAAABAOExcvVJIUJv9HuVfbV0y7lRPRARv4wDtcdhHG7QN40h5wQ2uwPF52OGQ8KY+66a1A/8lNKB75sgj2xj44s8lDQ==';
before(function () {
basecoin = bitgo.coin('txlm');
const walletData = {
id: '5a78dd561c6258a907f1eeaee132f796',
users: [
{
user: '543c11ed356d00cb7600000b98794503',
permissions: ['admin', 'view', 'spend'],
},
],
coin: 'txlm',
label: 'Verification Wallet',
m: 2,
n: 3,
keys: [
'5a78dd56bfe424aa07aa068651b194fd',
'5a78dd5674a70eb4079f58797dfe2f5e',
'5a78dd561c6258a907f1eea9f1d079e2',
],
tags: ['5a78dd561c6258a907f1eeaee132f796'],
disableTransactionNotifications: false,
freeze: {},
deleted: false,
approvalsRequired: 1,
isCold: true,
coinSpecific: {},
clientFlags: [],
balance: 650000000,
confirmedBalance: 650000000,
spendableBalance: 650000000,
balanceString: '650000000',
confirmedBalanceString: '650000000',
spendableBalanceString: '650000000',
receiveAddress: {
id: '5a78de2bbfe424aa07aa131ec03c8dc1',
address: 'GBSGOXQNPULNPBX7XTHXP2GDBOZMHPIZOOA7CQXYTLHYKF5J2JDCF7LT',
chain: 0,
index: 0,
coin: 'txlm',
wallet: '5a78dd561c6258a907f1eeaee132f796',
coinSpecific: {},
},
pendingApprovals: [],
};
wallet = new Wallet(bitgo, basecoin, walletData);
});
it('should sign a prebuild', async function () {
// sign transaction
halfSignedTransaction = await wallet.signTransaction({
txPrebuild: prebuild,
prv: userKeychain.prv,
});
halfSignedTransaction.halfSigned.txBase64.should.equal(signedTxBase64);
});
it('should sign a prebuild with root key', async function () {
rootKeyHalfSignedTransaction = await wallet.signTransaction({
txPrebuild: prebuild,
prv: rootKeychain.prv,
});
rootKeyHalfSignedTransaction.halfSigned.txBase64.should.equal(signedTxBase64);
});
it('should sign a transaction with generated root key pair', async function () {
const seed = Buffer.from(rootKeychain.prv.slice(0, 64), 'hex');
const kp = basecoin.generateRootKeyPair(seed);
kp.prv.length.should.equal(128);
const halfSignedTx = await wallet.signTransaction({
txPrebuild: prebuild,
prv: kp.prv,
});
halfSignedTx.halfSigned.txBase64.should.equal(signedTxBase64);
});
it('should verify the user signature on a tx', function () {
const userPub = userKeychain.pub;
const tx = new stellar.Transaction(halfSignedTransaction.halfSigned.txBase64, stellar.Networks.TESTNET);
const validSignature = basecoin.verifySignature(userPub, tx.hash(), tx.signatures[0].signature());
validSignature.should.equal(true);
});
it('should verify the user signature on a tx given root key', function () {
const rootPub = rootKeychain.pub;
const tx = new stellar.Transaction(rootKeyHalfSignedTransaction.halfSigned.txBase64, stellar.Networks.TESTNET);
const validSignature = basecoin.verifySignature(rootPub, tx.hash(), tx.signatures[0].signature());
validSignature.should.equal(true);
});
it('should fail to verify the wrong signature on a tx', function () {
const keyPair = basecoin.generateKeyPair();
const tx = new stellar.Transaction(halfSignedTransaction.halfSigned.txBase64, stellar.Networks.TESTNET);
const validSignature = basecoin.verifySignature(keyPair.pub, tx.hash(), tx.signatures[0].signature());
validSignature.should.equal(false);
});
it('should fail to verify the wrong signature on a tx given root key', function () {
const keyPair = basecoin.generateRootKeyPair();
const tx = new stellar.Transaction(rootKeyHalfSignedTransaction.halfSigned.txBase64, stellar.Networks.TESTNET);
const validSignature = basecoin.verifySignature(keyPair.pub, tx.hash(), tx.signatures[0].signature());
validSignature.should.equal(false);
});
it('should create a recovery transaction', async function () {
const destinationAddress = 'GDDHCKMYYYCVXOSAVMSEIYGYNX74LIAV3ACXYQ6WPMDUF7W3KZNWTHTH';
nock('https://horizon-testnet.stellar.org/accounts')
.get('/' + wallet.receiveAddress())
.reply(200, {
sequence: '35995558267060226',
balances: [
{
asset_type: 'native',
balance: '6500000000',
},
],
});
nock('https://horizon-testnet.stellar.org/accounts')
.get('/' + destinationAddress)
.reply(200, {
sequence: '35995558267060213',
balances: 13131313,
});
nock('https://horizon-testnet.stellar.org')
.get('/ledgers')
.query({
order: 'desc',
limit: 1,
})
.reply(200, {
records: [
{
base_reserve_in_stroops: '5000000',
base_fee_in_stroops: 100,
},
],
})
.persist();
const recovery = await basecoin.recover({
userKey: 'GA34NPQ4M54HHZBKSDZ5B3J3BZHTXKCZD4UFO2OYZERPOASK4DAATSIB',
backupKey: 'GC3D3ZNNK7GHLMSWJA54DQO6QJUJJF7K6J5JGCEW45ZT6QMKZ6PMUHUM',
recoveryDestination: destinationAddress,
rootAddress: wallet.receiveAddress(),
});
should.exist(recovery.txBase64);
should.exist(recovery.feeInfo);
recovery.coin.should.equal('txlm');
recovery.txBase64.should.be.a.String();
recovery.recoveryAmount.should.be.a.Number();
recovery.feeInfo.fee.should.equal(100);
});
it('should fail to verify a transaction signed with the wrong key', async function () {
// sign transaction
const tx = await wallet.signTransaction({
txPrebuild: prebuild,
prv: backupKeychain.prv,
});
const txParams = {
recipients: [
{
address: 'GCNFRU774FPHLV3HAB6CR54XJYFYITOLU6KS2J5BNCLDPYN7I3DOMIPY',
amount: '128000000',
},
],
};
const txPrebuild = {
txBase64: tx.halfSigned.txBase64,
};
const verification = {
disableNetworking: true,
keychains: {
user: { pub: userKeychain.pub },
backup: { pub: backupKeychain.pub },
},
};
await basecoin
.verifyTransaction({ txParams, txPrebuild, wallet, verification })
.should.be.rejectedWith('transaction signed with wrong key');
});
it('should fail to verify a transaction signed with the wrong root key', async function () {
// sign transaction
const tx = await wallet.signTransaction({
txPrebuild: prebuild,
prv: backupRootKeychain.prv,
});
const txParams = {
recipients: [
{
address: 'GCNFRU774FPHLV3HAB6CR54XJYFYITOLU6KS2J5BNCLDPYN7I3DOMIPY',
amount: '128000000',
},
],
};
const txPrebuild = {
txBase64: tx.halfSigned.txBase64,
};
const verification = {
disableNetworking: true,
keychains: {
user: { pub: rootKeychain.pub },
backup: { pub: backupRootKeychain.pub },
},
};
await basecoin
.verifyTransaction({ txParams, txPrebuild, wallet, verification })
.should.be.rejectedWith('transaction signed with wrong key');
});
it('should fail to verify a transaction to the wrong recipient', async function () {
// sign transaction
const tx = await wallet.signTransaction({
txPrebuild: prebuild,
prv: backupKeychain.prv,
});
const txParams = {
recipients: [
{
address: 'GAK3NSB43EVCZKDH4PYGJPCVPOYZ7X7KIR3ZTWSYRKRMJWGG5TABM6TH',
amount: '128000000',
},
],
};
const txPrebuild = {
txBase64: tx.halfSigned.txBase64,
};
const verification = {
disableNetworking: true,
keychains: {
user: { pub: userKeychain.pub },
backup: { pub: backupKeychain.pub },
},
};
await basecoin
.verifyTransaction({ txParams, txPrebuild, wallet, verification })
.should.be.rejectedWith('transaction prebuild does not match expected recipient');
});
it('should fail to verify a transaction with the wrong amount', async function () {
// sign transaction
const tx = await wallet.signTransaction({
txPrebuild: prebuild,
prv: backupKeychain.prv,
});
const txParams = {
recipients: [
{
address: 'GCNFRU774FPHLV3HAB6CR54XJYFYITOLU6KS2J5BNCLDPYN7I3DOMIPY',
amount: '130000000',
},
],
};
const txPrebuild = {
txBase64: tx.halfSigned.txBase64,
};
const verification = {
disableNetworking: true,
keychains: {
user: { pub: userKeychain.pub },
backup: { pub: backupKeychain.pub },
},
};
await basecoin
.verifyTransaction({ txParams, txPrebuild, wallet, verification })
.should.be.rejectedWith('transaction prebuild does not match expected amount');
});
it('should fail to verify a transaction without recipients', async function () {
const prebuilt = {
txBase64:
'AAAAAP1qe44j+i4uIT+arbD4QDQBt8ryEeJd7a0jskQ3nwDeAAAAAAB/4cUAAAACAAAAAAAAAAIAAAAAAAAAAQAAAAAAAAAAAAAAAA==',
txInfo: {
fee: 0,
sequence: '35995558267060226',
source: 'GD6WU64OEP5C4LRBH6NK3MHYIA2ADN6K6II6EXPNVUR3ERBXT4AN4ACD',
operations: [],
signatures: [],
},
feeInfo: {
height: 123456,
xlmBaseFee: '100',
xlmBaseReserve: '5000000',
},
walletId: '5a78dd561c6258a907f1eeaee132f796',
};
const keyPair = {
pub: 'GAA4LVBE2HEKECNWDRT2NLTSBWFIZRGTEQFC7BLOREMMPNDHFRUGP3VZ',
prv: 'SCIVSTUJX7SYJZHKMJI4YF7YWA27FU7XN5EH4OWBFL2Y2KTYI7IP2DFZ',
};
// sign transaction
const tx = await wallet.signTransaction({
txPrebuild: prebuilt,
prv: keyPair.prv,
});
const txParams = {
recipients: [
{
address: 'GAUKA3ZTH3DZ6THBCPL6AOQBCEEBIFYDU4FGXUCHOC7PILXGUPTUBJ6E',
amount: '130000000',
},
],
};
const txPrebuild = {
txBase64: tx.halfSigned.txBase64,
};
const verification = {
disableNetworking: true,
keychains: {
user: { pub: userKeychain.pub },
backup: { pub: backupKeychain.pub },
},
};
await basecoin
.verifyTransaction({ txParams, txPrebuild, wallet, verification })
.should.be.rejectedWith('transaction prebuild does not have any operations');
});
it('should verify a transaction', async function () {
const txParams = {
recipients: [
{
address: 'GCNFRU774FPHLV3HAB6CR54XJYFYITOLU6KS2J5BNCLDPYN7I3DOMIPY',
amount: '128000000',
},
],
};
const txPrebuild = {
txBase64: halfSignedTransaction.halfSigned.txBase64,
};
const verification = {
disableNetworking: true,
keychains: {
user: { pub: userKeychain.pub },
backup: { pub: backupKeychain.pub },
},
};
const validTransaction = await basecoin.verifyTransaction({ txParams, txPrebuild, wallet, verification });
validTransaction.should.equal(true);
});
it('should verify a transaction with root key', async function () {
const txParams = {
recipients: [
{
address: 'GCNFRU774FPHLV3HAB6CR54XJYFYITOLU6KS2J5BNCLDPYN7I3DOMIPY',
amount: '128000000',
},
],
};
const txPrebuild = {
txBase64: rootKeyHalfSignedTransaction.halfSigned.txBase64,
};
const verification = {
disableNetworking: true,
keychains: {
user: { pub: rootKeychain.pub },
backup: { pub: backupRootKeychain.pub },
},
};
const validTransaction = await basecoin.verifyTransaction({ txParams, txPrebuild, wallet, verification });
validTransaction.should.equal(true);
});
describe('trustline transactions', function () {
it('should fail to verify a trustline transaction with unmatching number of trustlines', async function () {
const txParams = {
recipients: [],
type: 'trustline',
trustlines: [],
};
const buildResult = {
txBase64:
'AAAAANsKrHV2BVjACFt2xlyhxYzP2MNBmb4IQ5E9/WiJiV3TAAABLAAM4aEAAAAHAAAAAAAAAAAAAAABAAAAAAAAAAYAAAABQlNUAAAAAABhNDpbuY4frrgwVQqkws7jxK+k4IMrJ6BaE0OFUva9vwAAAOjUpRAAAAAAAAAAAAA=',
};
nock(uri).post(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/tx/build`).reply(200, buildResult);
const txPrebuild = await wallet.prebuildTransaction(txParams);
const verification = {
disableNetworking: true,
keychains: {
user: { pub: userKeychain.pub },
backup: { pub: backupKeychain.pub },
},
};
await basecoin
.verifyTransaction({ txParams, txPrebuild, wallet, verification })
.should.be.rejectedWith('transaction prebuild does not match expected trustline operations');
});
it('should fail to verify a trustline transaction with unmatching trustlines', async function () {
const txParams = {
type: 'trustline',
recipients: [],
trustlines: [
{
token: 'txlm:BST-GBQTIOS3XGHB7LVYGBKQVJGCZ3R4JL5E4CBSWJ5ALIJUHBKS6263644L',
action: 'remove',
},
],
};
const buildResult = {
txBase64:
'AAAAANsKrHV2BVjACFt2xlyhxYzP2MNBmb4IQ5E9/WiJiV3TAAABLAAM4aEAAAAHAAAAAAAAAAAAAAABAAAAAAAAAAYAAAABQlNUAAAAAABhNDpbuY4frrgwVQqkws7jxK+k4IMrJ6BaE0OFUva9vwAAAOjUpRAAAAAAAAAAAAA=',
};
nock(uri).post(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/tx/build`).reply(200, buildResult);
const txPrebuild = await wallet.prebuildTransaction(txParams);
const verification = {
disableNetworking: true,
keychains: {
user: { pub: userKeychain.pub },
backup: { pub: backupKeychain.pub },
},
};
await basecoin
.verifyTransaction({ txParams, txPrebuild, wallet, verification })
.should.be.rejectedWith('transaction prebuild does not match expected trustline tokens');
});
it('should fail to verify a trustline transaction with unmatching limit', async function () {
const txParams = {
type: 'trustline',
recipients: [],
trustlines: [
{
token: 'txlm:BST-GBQTIOS3XGHB7LVYGBKQVJGCZ3R4JL5E4CBSWJ5ALIJUHBKS6263644L',
action: 'add',
limit: '999',
},
],
};
const buildResult = {
txBase64:
'AAAAANsKrHV2BVjACFt2xlyhxYzP2MNBmb4IQ5E9/WiJiV3TAAABLAAM4aEAAAAHAAAAAAAAAAAAAAABAAAAAAAAAAYAAAABQlNUAAAAAABhNDpbuY4frrgwVQqkws7jxK+k4IMrJ6BaE0OFUva9vwAAAOjUpRAAAAAAAAAAAAA=',
};
nock(uri).post(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/tx/build`).reply(200, buildResult);
const txPrebuild = await wallet.prebuildTransaction(txParams);
const verification = {
disableNetworking: true,
keychains: {
user: { pub: userKeychain.pub },
backup: { pub: backupKeychain.pub },
},
};
await basecoin
.verifyTransaction({ txParams, txPrebuild, wallet, verification })
.should.be.rejectedWith('transaction prebuild does not match expected trustline tokens');
});
it('should verify a trustline transaction', async function () {
const txParams = {
type: 'trustline',
recipients: [],
trustlines: [
{
token: 'txlm:BST-GBQTIOS3XGHB7LVYGBKQVJGCZ3R4JL5E4CBSWJ5ALIJUHBKS6263644L',
action: 'add',
limit: '1000000000000',
},
{
token: 'txlm:TST-GBQTIOS3XGHB7LVYGBKQVJGCZ3R4JL5E4CBSWJ5ALIJUHBKS6263644L',
action: 'remove',
},
],
};
const buildResult = {
txBase64:
'AAAAANsKrHV2BVjACFt2xlyhxYzP2MNBmb4IQ5E9/WiJiV3TAAAAyAAM4aEAAAAJAAAAAAAAAAAAAAACAAAAAAAAAAYAAAABQlNUAAAAAABhNDpbuY4frrgwVQqkws7jxK+k4IMrJ6BaE0OFUva9vwAAAOjUpRAAAAAAAAAAAAYAAAABVFNUAAAAAABhNDpbuY4frrgwVQqkws7jxK+k4IMrJ6BaE0OFUva9vwAAAAAAAAAAAAAAAAAAAAA=',
};
nock(uri).post(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/tx/build`).reply(200, buildResult);
const txPrebuild = await wallet.prebuildTransaction(txParams);
const verification = {
disableNetworking: true,
keychains: {
user: { pub: userKeychain.pub },
backup: { pub: backupKeychain.pub },
},
};
const validTransaction = await basecoin.verifyTransaction({ txParams, txPrebuild, wallet, verification });
validTransaction.should.equal(true);
});
});
describe('enabletoken transactions', function () {
it('should fail to verify a enbletoken transaction with unmatching number of token', async function () {
const txParams = {
recipients: [],
type: 'enabletoken',
};
const buildResult = {
txBase64:
'AAAAANsKrHV2BVjACFt2xlyhxYzP2MNBmb4IQ5E9/WiJiV3TAAABLAAM4aEAAAAHAAAAAAAAAAAAAAABAAAAAAAAAAYAAAABQlNUAAAAAABhNDpbuY4frrgwVQqkws7jxK+k4IMrJ6BaE0OFUva9vwAAAOjUpRAAAAAAAAAAAAA=',
};
nock(uri).post(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/tx/build`).reply(200, buildResult);
const txPrebuild = await wallet.prebuildTransaction(txParams);
const verification = {
disableNetworking: true,
keychains: {
user: { pub: userKeychain.pub },
backup: { pub: backupKeychain.pub },
},
};
await basecoin
.verifyTransaction({ txParams, txPrebuild, wallet, verification })
.should.be.rejectedWith('transaction prebuild does not match expected trustline operations');
});
it('should fail to verify a enbletoken transaction with unmatching token', async function () {
const txParams = {
type: 'enabletoken',
recipients: [
{
token: 'txlm:BST-GBQTIOS3XGHB7LVYGBKQVJGCZ3R4JL5E4CBSWJ5ALIJUHBKS6263644L',
amount: 0,
address: '',
},
],
};
const buildResult = {
txBase64:
'AAAAANsKrHV2BVjACFt2xlyhxYzP2MNBmb4IQ5E9/WiJiV3TAAABLAAM4aEAAAAHAAAAAAAAAAAAAAABAAAAAAAAAAYAAAABQlNUAAAAAABhNDpbuY4frrgwVQqkws7jxK+k4IMrJ6BaE0OFUva9vwAAAOjUpRAAAAAAAAAAAAA=',
};
nock(uri).post(`/api/v2/${wallet.coin()}/wallet/${wallet.id()}/tx/build`).reply(200, buildResult);
const txPrebuild = await wallet.prebuildTransaction(txParams);
const verification = {
disableNetworking: true,
keychains: {
user: { pub: userKeychain.pub },
backup: { pub: backupKeychain.pub },
},
};
await basecoin
.verifyTransaction({ txParams, txPrebuild, wallet, verification })
.should.be.rejectedWith('transaction prebuild does not match expected trustline tokens');
});
});
});
describe('Federation lookups:', function () {
describe('Look up by stellar address:', function () {
it('should fail to loop up an invalid stellar address with a bitgo.com domain', async function () {
const stellarAddress = 'invalid*bitgo.com';
nock(uri)
.get('/.well-known/stellar.toml')
.reply(200, 'FEDERATION_SERVER="https://test.bitgo.com/api/v2/txlm/federation"')
.get('/api/v2/txlm/federation')
.query({
q: stellarAddress,
type: 'name',
})
.reply(404, {
detail: `user not found: ${stellarAddress}`,
name: 'UserNotFound',
});
await basecoin
.federationLookupByName(stellarAddress)
.should.be.rejectedWith(`user not found: ${stellarAddress}`);
});
it('should resolve a stellar address into an account', async function () {
const stellarAddress = 'tester*bitgo.com';
const accountId = 'GCBYY3S62QY43PMEKGJHRCBHEFJOHCLGSMWXREUZYDQHJHQ2LK4I42JA';
nock(uri)
.get('/.well-known/stellar.toml')
.reply(200, 'FEDERATION_SERVER="https://test.bitgo.com/api/v2/txlm/federation"')
.get('/api/v2/txlm/federation')
.query({
q: stellarAddress,
type: 'name',
})
.reply(200, {
stellar_address: stellarAddress,
account_id: accountId,
});
const res = await basecoin.federationLookupByName(stellarAddress);
res.should.have.property('stellar_address');
res.should.have.property('account_id');
res.stellar_address.should.equal(stellarAddress);
res.account_id.should.equal(accountId);
});
});
describe('Look up by account id:', function () {
it('should fail to look up an account if the account id is invalid', async function () {
const accountId = '123';
nock(uri)
.get('/api/v2/txlm/federation')
.query({
q: accountId,
type: 'id',
})
.reply(400, {
detail: 'invalid id: ' + accountId,
});
await basecoin.federationLookupByAccountId(accountId).should.be.rejectedWith(`invalid id: ${accountId}`);
});
it('should return only account_id for non-bitgo accounts', async function () {
const accountId = 'GCROXHYJSTCS3CQQIU7GFC7YQIRIVGPYZQRZEM6PN7P7TAZ3PU4CHJRG';
nock(uri)
.get('/api/v2/txlm/federation')
.query({
q: accountId,
type: 'id',
})
.reply(200, {
account_id: accountId,
});
const res = await basecoin.federationLookupByAccountId(accountId);
res.should.not.have.property('stellar_address');
res.should.not.have.property('memo_type');
res.should.not.have.property('memo');
res.should.have.property('account_id');
res.account_id.should.equal(accountId);
});
it('should resolve a valid account id into an account', async function () {
const accountId = 'GDDHCKMYYYCVXOSAVMSEIYGYNX74LIAV3ACXYQ6WPMDUF7W3KZNWTHTH';
nock(uri)
.get('/api/v2/txlm/federation')
.query({
q: accountId,
type: 'id',
})
.reply(200, {
stellar_address: 'tester*bitgo.com',
account_id: accountId,
memo_type: 'id',
memo: '0',
});
const res = await basecoin.federationLookupByAccountId(accountId);
res.should.have.property('stellar_address');
res.should.have.property('account_id');
res.should.have.property('memo_type');
res.should.have.property('memo');
res.account_id.should.equal(accountId);
res.stellar_address.should.equal('tester*bitgo.com');
});
});
});
describe('Keypairs:', () => {
it('should generate a keypair from random seed', function () {
const keyPair = basecoin.generateKeyPair();
keyPair.should.have.property('pub');
keyPair.should.have.property('prv');
const address = keyPair.pub;
basecoin.isValidAddress(address).should.equal(true);
basecoin.isValidPub(keyPair.pub).should.equal(true);
basecoin.isValidPrv(keyPair.prv).should.equal(true);
});
it('should generate a keypair from seed', function () {
const seed = randomBytes(32);
const keyPair = basecoin.generateKeyPair(seed);
keyPair.should.have.property('pub');
keyPair.should.have.property('prv');
const address = keyPair.pub;
basecoin.isValidAddress(address).should.equal(true);
basecoin.isValidPub(keyPair.pub).should.equal(true);
basecoin.isValidPrv(keyPair.prv).should.equal(true);
const secret = keyPair.prv;
stellar.StrKey.encodeEd25519SecretSeed(seed).should.equal(secret);
});
it('should validate pub key', () => {
const { pub } = basecoin.keychains().create();
basecoin.isValidPub(pub).should.equal(true);
});
});
describe('Generate wallet Root key pair: ', () => {
it('should generate a root keypair from random seed', function () {
const kp = basecoin.generateRootKeyPair();
basecoin.isValidPub(kp.pub).should.equal(true);
const keyPair = new KeyPair({ prv: kp.prv }).getKeys(true);
keyPair.should.have.property('pub');
keyPair.should.have.property('prv');
keyPair.prv?.should.equal(kp.prv.slice(0, 64));
keyPair.pub.should.equal(kp.pub);
});
it('should generate a root keypair from seed', function () {
const seed = Buffer.from('761de570c460792f10378a8b3c7cc2283241db37d8dac13dbdd8095a05ea00b2', 'hex');
const kp = basecoin.generateRootKeyPair(seed);
basecoin.isValidPub(kp.pub).should.equal(true);
kp.pub.should.equal('7fe4254baaeebfefd5a632fdb71aa9ec63aa611bcd392b07a759a4b21307b7fc');
kp.prv.should.equal(
'761de570c460792f10378a8b3c7cc2283241db37d8dac13dbdd8095a05ea00b27fe4254baaeebfefd5a632fdb71aa9ec63aa611bcd392b07a759a4b21307b7fc'
);
const keyPair = new KeyPair({ prv: kp.prv }).getKeys(true);
keyPair.should.have.property('prv');
keyPair.prv?.should.equal(kp.prv.slice(0, 64));
keyPair.pub.should.equal(kp.pub);
});
});
});
Выполнить команду
Для локальной разработки. Не используйте в интернете!