Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SEP 0023 support. #327

Merged
merged 4 commits into from
Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions src/strkey.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ const versionBytes = {
ed25519PublicKey: 6 << 3, // G
ed25519SecretSeed: 18 << 3, // S
preAuthTx: 19 << 3, // T
sha256Hash: 23 << 3 // X
sha256Hash: 23 << 3, // X
muxedAccount: 12 << 3 // M
};

/**
Expand Down Expand Up @@ -107,6 +108,24 @@ export class StrKey {
static decodeSha256Hash(data) {
return decodeCheck('sha256Hash', data);
}

/**
* Encodes data to strkey muxed account.
* @param {Buffer} data data to encode
* @returns {string}
*/
static encodeMuxedAccount(data) {
return encodeCheck('muxedAccount', data);
}

/**
* Decodes strkey muxed account to raw data.
* @param {string} data data to decode
* @returns {Buffer}
*/
static decodeMuxedAccount(data) {
return decodeCheck('muxedAccount', data);
}
}

function isValid(versionByteName, encoded) {
Expand Down Expand Up @@ -144,7 +163,7 @@ export function decodeCheck(versionByteName, encoded) {

if (isUndefined(expectedVersion)) {
throw new Error(
`${versionByteName} is not a valid version byte name. expected one of "accountId" or "seed"`
`${versionByteName} is not a valid version byte name. Expected one of "ed25519PublicKey", "ed25519SecretSeed", "preAuthTx", "sha256Hash", "muxedAccount"`
);
}

Expand Down Expand Up @@ -172,11 +191,11 @@ export function encodeCheck(versionByteName, data) {

if (isUndefined(versionByte)) {
throw new Error(
`${versionByteName} is not a valid version byte name. expected one of "ed25519PublicKey", "ed25519SecretSeed", "preAuthTx", "sha256Hash"`
`${versionByteName} is not a valid version byte name. Expected one of "ed25519PublicKey", "ed25519SecretSeed", "preAuthTx", "sha256Hash", "muxedAccount"`
);
}

data = Buffer.from(data);

const versionBuffer = Buffer.from([versionByte]);
const payload = Buffer.concat([versionBuffer, data]);
const checksum = calculateChecksum(payload);
Expand Down
253 changes: 204 additions & 49 deletions test/unit/strkey_test.js
Original file line number Diff line number Diff line change
@@ -1,81 +1,238 @@
describe('StrKey', function() {
before(function() {
StellarBase.Network.useTestNetwork();
var keypair = StellarBase.Keypair.master();
this.unencodedBuffer = keypair.rawPublicKey();
this.unencoded = this.unencodedBuffer.toString();
var keypair = StellarBase.Keypair.master(
'Test SDF Network ; September 2015'
);
this.unencodedBuffer = keypair.rawPublicKey();
this.unencoded = this.unencodedBuffer.toString();
this.accountIdEncoded = keypair.publicKey();
this.seedEncoded = StellarBase.StrKey.encodeEd25519SecretSeed(this.unencodedBuffer);
})

this.seedEncoded = StellarBase.StrKey.encodeEd25519SecretSeed(
this.unencodedBuffer
);
});
after(function() {
StellarBase.Network.use(null);
})
});

describe('#decodeCheck', function() {
it("decodes correctly", function() {
expect(StellarBase.StrKey.decodeEd25519PublicKey(this.accountIdEncoded)).to.eql(this.unencodedBuffer);
expect(StellarBase.StrKey.decodeEd25519SecretSeed(this.seedEncoded)).to.eql(this.unencodedBuffer);
it('decodes correctly', function() {
expect(
StellarBase.StrKey.decodeEd25519PublicKey(this.accountIdEncoded)
).to.eql(this.unencodedBuffer);
expect(
StellarBase.StrKey.decodeEd25519SecretSeed(this.seedEncoded)
).to.eql(this.unencodedBuffer);
});

it("throws an error when the version byte is wrong", function() {
expect(() => StellarBase.StrKey.decodeEd25519SecretSeed("GBPXXOA5N4JYPESHAADMQKBPWZWQDQ64ZV6ZL2S3LAGW4SY7NTCMWIVL")).to.throw(/invalid version/);
expect(() => StellarBase.StrKey.decodeEd25519PublicKey("SBGWKM3CD4IL47QN6X54N6Y33T3JDNVI6AIJ6CD5IM47HG3IG4O36XCU")).to.throw(/invalid version/);
it('throws an error when the version byte is wrong', function() {
expect(() =>
StellarBase.StrKey.decodeEd25519SecretSeed(
'GBPXXOA5N4JYPESHAADMQKBPWZWQDQ64ZV6ZL2S3LAGW4SY7NTCMWIVL'
)
).to.throw(/invalid version/);
expect(() =>
StellarBase.StrKey.decodeEd25519PublicKey(
'SBGWKM3CD4IL47QN6X54N6Y33T3JDNVI6AIJ6CD5IM47HG3IG4O36XCU'
)
).to.throw(/invalid version/);
});

it("throws an error when decoded data encodes to other string", function() {
it('throws an error when decoded data encodes to other string', function() {
// accountId
expect(() => StellarBase.StrKey.decodeEd25519PublicKey("GBPXX0A5N4JYPESHAADMQKBPWZWQDQ64ZV6ZL2S3LAGW4SY7NTCMWIVL")).to.throw(/invalid encoded string/);
expect(() => StellarBase.StrKey.decodeEd25519PublicKey("GCFZB6L25D26RQFDWSSBDEYQ32JHLRMTT44ZYE3DZQUTYOL7WY43PLBG++")).to.throw(/invalid encoded string/);
expect(() => StellarBase.StrKey.decodeEd25519PublicKey("GADE5QJ2TY7S5ZB65Q43DFGWYWCPHIYDJ2326KZGAGBN7AE5UY6JVDRRA")).to.throw(/invalid encoded string/);
expect(() => StellarBase.StrKey.decodeEd25519PublicKey("GB6OWYST45X57HCJY5XWOHDEBULB6XUROWPIKW77L5DSNANBEQGUPADT2")).to.throw(/invalid encoded string/);
expect(() => StellarBase.StrKey.decodeEd25519PublicKey("GB6OWYST45X57HCJY5XWOHDEBULB6XUROWPIKW77L5DSNANBEQGUPADT2T")).to.throw(/invalid encoded string/);
expect(() =>
StellarBase.StrKey.decodeEd25519PublicKey(
'GBPXX0A5N4JYPESHAADMQKBPWZWQDQ64ZV6ZL2S3LAGW4SY7NTCMWIVL'
)
).to.throw(/invalid encoded string/);
expect(() =>
StellarBase.StrKey.decodeEd25519PublicKey(
'GCFZB6L25D26RQFDWSSBDEYQ32JHLRMTT44ZYE3DZQUTYOL7WY43PLBG++'
)
).to.throw(/invalid encoded string/);
expect(() =>
StellarBase.StrKey.decodeEd25519PublicKey(
'GADE5QJ2TY7S5ZB65Q43DFGWYWCPHIYDJ2326KZGAGBN7AE5UY6JVDRRA'
)
).to.throw(/invalid encoded string/);
expect(() =>
StellarBase.StrKey.decodeEd25519PublicKey(
'GB6OWYST45X57HCJY5XWOHDEBULB6XUROWPIKW77L5DSNANBEQGUPADT2'
)
).to.throw(/invalid encoded string/);
expect(() =>
StellarBase.StrKey.decodeEd25519PublicKey(
'GB6OWYST45X57HCJY5XWOHDEBULB6XUROWPIKW77L5DSNANBEQGUPADT2T'
)
).to.throw(/invalid encoded string/);
// seed
expect(() => StellarBase.StrKey.decodeEd25519SecretSeed("SB7OJNF5727F3RJUG5ASQJ3LUM44ELLNKW35ZZQDHMVUUQNGYW")).to.throw(/invalid encoded string/);
expect(() => StellarBase.StrKey.decodeEd25519SecretSeed("SB7OJNF5727F3RJUG5ASQJ3LUM44ELLNKW35ZZQDHMVUUQNGYWMEGB2W2")).to.throw(/invalid encoded string/);
expect(() => StellarBase.StrKey.decodeEd25519SecretSeed("SB7OJNF5727F3RJUG5ASQJ3LUM44ELLNKW35ZZQDHMVUUQNGYWMEGB2W2T")).to.throw(/invalid encoded string/);
expect(() => StellarBase.StrKey.decodeEd25519SecretSeed("SCMB30FQCIQAWZ4WQTS6SVK37LGMAFJGXOZIHTH2PY6EXLP37G46H6DT")).to.throw(/invalid encoded string/);
expect(() => StellarBase.StrKey.decodeEd25519SecretSeed("SAYC2LQ322EEHZYWNSKBEW6N66IRTDREEBUXXU5HPVZGMAXKLIZNM45H++")).to.throw(/invalid encoded string/);
expect(() =>
StellarBase.StrKey.decodeEd25519SecretSeed(
'SB7OJNF5727F3RJUG5ASQJ3LUM44ELLNKW35ZZQDHMVUUQNGYW'
)
).to.throw(/invalid encoded string/);
expect(() =>
StellarBase.StrKey.decodeEd25519SecretSeed(
'SB7OJNF5727F3RJUG5ASQJ3LUM44ELLNKW35ZZQDHMVUUQNGYWMEGB2W2'
)
).to.throw(/invalid encoded string/);
expect(() =>
StellarBase.StrKey.decodeEd25519SecretSeed(
'SB7OJNF5727F3RJUG5ASQJ3LUM44ELLNKW35ZZQDHMVUUQNGYWMEGB2W2T'
)
).to.throw(/invalid encoded string/);
expect(() =>
StellarBase.StrKey.decodeEd25519SecretSeed(
'SCMB30FQCIQAWZ4WQTS6SVK37LGMAFJGXOZIHTH2PY6EXLP37G46H6DT'
)
).to.throw(/invalid encoded string/);
expect(() =>
StellarBase.StrKey.decodeEd25519SecretSeed(
'SAYC2LQ322EEHZYWNSKBEW6N66IRTDREEBUXXU5HPVZGMAXKLIZNM45H++'
)
).to.throw(/invalid encoded string/);
});

it("throws an error when the checksum is wrong", function() {
expect(() => StellarBase.StrKey.decodeEd25519PublicKey("GBPXXOA5N4JYPESHAADMQKBPWZWQDQ64ZV6ZL2S3LAGW4SY7NTCMWIVT")).to.throw(/invalid checksum/);
expect(() => StellarBase.StrKey.decodeEd25519SecretSeed("SBGWKM3CD4IL47QN6X54N6Y33T3JDNVI6AIJ6CD5IM47HG3IG4O36XCX")).to.throw(/invalid checksum/);
it('throws an error when the checksum is wrong', function() {
expect(() =>
StellarBase.StrKey.decodeEd25519PublicKey(
'GBPXXOA5N4JYPESHAADMQKBPWZWQDQ64ZV6ZL2S3LAGW4SY7NTCMWIVT'
)
).to.throw(/invalid checksum/);
expect(() =>
StellarBase.StrKey.decodeEd25519SecretSeed(
'SBGWKM3CD4IL47QN6X54N6Y33T3JDNVI6AIJ6CD5IM47HG3IG4O36XCX'
)
).to.throw(/invalid checksum/);
});

describe('muxed account', function() {
it('decodes correctly', function() {
const med25519 = new StellarBase.xdr.MuxedAccountMed25519({
id: StellarBase.xdr.Uint64.fromString('0'),
ed25519: StellarBase.StrKey.decodeEd25519PublicKey(
'GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ'
)
});
let expectedBuffer = med25519.toXDR();
let strkey =
'MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL6';
expect(StellarBase.StrKey.decodeMuxedAccount(strkey)).to.eql(
expectedBuffer
);
});

it('throws an error: unused trailing bit must be zero in the encoding of the last three bytes (24 bits) as five base-32 symbols (25 bits)', function() {
// unused trailing bit must be zero in the encoding of the last three bytes (24 bits) as five base-32 symbols (25 bits)
expect(() =>
StellarBase.StrKey.decodeMuxedAccount(
'MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL7'
)
).to.throw(/invalid encoded string/);
});

it('throws an error if strkey has an invalid algorithm', function() {
// Invalid algorithm (low 3 bits of version byte are 7)
expect(() =>
StellarBase.StrKey.decodeMuxedAccount(
'M4AAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITIU2K'
)
).to.throw(/invalid version byte/);
});

it('throws an error if strkey has an invalid length', function() {
expect(() =>
StellarBase.StrKey.decodeMuxedAccount(
'MCAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITKNOGA'
)
).to.throw(/invalid encoded string/);
});

it('throws an error with if strkey has padding bytes ', function() {
expect(() =>
StellarBase.StrKey.decodeMuxedAccount(
'MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL6==='
)
).to.throw(/invalid encoded string/);
});

it('throws an error with an invalid checksum', function() {
expect(() =>
StellarBase.StrKey.decodeMuxedAccount(
'MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL4'
)
).to.throw(/invalid checksum/);
});
});
});

describe('#encodeCheck', function() {
it("encodes a buffer correctly", function() {
expect(StellarBase.StrKey.encodeEd25519PublicKey(this.unencodedBuffer)).to.eql(this.accountIdEncoded);
expect(StellarBase.StrKey.encodeEd25519PublicKey(this.unencodedBuffer)).to.match(/^G/);
expect(StellarBase.StrKey.encodeEd25519SecretSeed(this.unencodedBuffer)).to.eql(this.seedEncoded);
expect(StellarBase.StrKey.encodeEd25519SecretSeed(this.unencodedBuffer)).to.match(/^S/);
it('encodes a buffer correctly', function() {
expect(
StellarBase.StrKey.encodeEd25519PublicKey(this.unencodedBuffer)
).to.eql(this.accountIdEncoded);
expect(
StellarBase.StrKey.encodeEd25519PublicKey(this.unencodedBuffer)
).to.match(/^G/);
expect(
StellarBase.StrKey.encodeEd25519SecretSeed(this.unencodedBuffer)
).to.eql(this.seedEncoded);
expect(
StellarBase.StrKey.encodeEd25519SecretSeed(this.unencodedBuffer)
).to.match(/^S/);

var strkeyEncoded;

strkeyEncoded = StellarBase.StrKey.encodePreAuthTx(this.unencodedBuffer);
expect(strkeyEncoded).to.match(/^T/);
expect(StellarBase.StrKey.decodePreAuthTx(strkeyEncoded)).to.eql(this.unencodedBuffer);
expect(StellarBase.StrKey.decodePreAuthTx(strkeyEncoded)).to.eql(
this.unencodedBuffer
);

strkeyEncoded = StellarBase.StrKey.encodeSha256Hash(this.unencodedBuffer);
expect(strkeyEncoded).to.match(/^X/);
expect(StellarBase.StrKey.decodeSha256Hash(strkeyEncoded)).to.eql(this.unencodedBuffer);
expect(StellarBase.StrKey.decodeSha256Hash(strkeyEncoded)).to.eql(
this.unencodedBuffer
);
});

it("encodes a buffer correctly", function() {
expect(StellarBase.StrKey.encodeEd25519PublicKey(this.unencodedBuffer)).to.eql(this.accountIdEncoded);
expect(StellarBase.StrKey.encodeEd25519SecretSeed(this.unencodedBuffer)).to.eql(this.seedEncoded);
it('encodes a buffer correctly', function() {
expect(
StellarBase.StrKey.encodeEd25519PublicKey(this.unencodedBuffer)
).to.eql(this.accountIdEncoded);
expect(
StellarBase.StrKey.encodeEd25519SecretSeed(this.unencodedBuffer)
).to.eql(this.seedEncoded);
});

it('throws an error when the data is null', function() {
expect(() => StellarBase.StrKey.encodeEd25519SecretSeed(null)).to.throw(
/null data/
);
expect(() => StellarBase.StrKey.encodeEd25519PublicKey(null)).to.throw(
/null data/
);
});

it("throws an error when the data is null", function() {
expect(() => StellarBase.StrKey.encodeEd25519SecretSeed(null)).to.throw(/null data/);
expect(() => StellarBase.StrKey.encodeEd25519PublicKey(null)).to.throw(/null data/);
describe('muxed account', function() {
it('encodes a buffer correctly', function() {
const med25519 = new StellarBase.xdr.MuxedAccountMed25519({
id: StellarBase.xdr.Uint64.fromString('0'),
ed25519: StellarBase.StrKey.decodeEd25519PublicKey(
'GA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVSGZ'
)
});
let buffer = med25519.toXDR();
let expectedMuxedAccount =
'MAAAAAAAAAAAAAB7BQ2L7E5NBWMXDUCMZSIPOBKRDSBYVLMXGSSKF6YNPIB7Y77ITLVL6';
expect(StellarBase.StrKey.encodeMuxedAccount(buffer)).to.eql(
expectedMuxedAccount
);
});
});
});

describe('#isValidEd25519PublicKey', function() {
it("returns true for valid public key", function() {
it('returns true for valid public key', function() {
var keys = [
'GBBM6BKZPEHWYO3E3YKREDPQXMS4VK35YLNU7NFBRI26RAN7GI5POFBB',
'GB7KKHHVYLDIZEKYJPAJUOTBE5E3NJAXPSDZK7O6O44WR3EBRO5HRPVT',
Expand All @@ -94,7 +251,7 @@ describe('StrKey', function() {
}
});

it("returns false for invalid public key", function() {
it('returns false for invalid public key', function() {
var keys = [
'GBPXX0A5N4JYPESHAADMQKBPWZWQDQ64ZV6ZL2S3LAGW4SY7NTCMWIVL',
'GCFZB6L25D26RQFDWSSBDEYQ32JHLRMTT44ZYE3DZQUTYOL7WY43PLBG++',
Expand All @@ -113,11 +270,10 @@ describe('StrKey', function() {
expect(StellarBase.StrKey.isValidEd25519PublicKey(keys[i])).to.be.false;
}
});

});

describe('#isValidSecretKey', function() {
it("returns true for valid secret key", function() {
it('returns true for valid secret key', function() {
var keys = [
'SAB5556L5AN5KSR5WF7UOEFDCIODEWEO7H2UR4S5R62DFTQOGLKOVZDY',
'SCZTUEKSEH2VYZQC6VLOTOM4ZDLMAGV4LUMH4AASZ4ORF27V2X64F2S2',
Expand All @@ -132,7 +288,7 @@ describe('StrKey', function() {
}
});

it("returns false for invalid secret key", function() {
it('returns false for invalid secret key', function() {
var keys = [
'GBBM6BKZPEHWYO3E3YKREDPQXMS4VK35YLNU7NFBRI26RAN7GI5POFBB',
'SAB5556L5AN5KSR5WF7UOEFDCIODEWEO7H2UR4S5R62DFTQOGLKOVZDYT', // Too long
Expand All @@ -143,10 +299,9 @@ describe('StrKey', function() {
];

for (var i in keys) {
expect(StellarBase.StrKey.isValidEd25519SecretSeed(keys[i])).to.be.false;
expect(StellarBase.StrKey.isValidEd25519SecretSeed(keys[i])).to.be
.false;
}
});

});

});