archethic
Version:
Archethic Javascript SDK
563 lines (517 loc) • 19 kB
JavaScript
import TransactionBuilder from "../lib/transaction_builder.js";
import { deriveKeyPair, deriveAddress, sign, verify } from "../lib/crypto.js"
import {
hexToUint8Array,
uint8ArrayToHex,
concatUint8Arrays,
encodeInt32,
encodeInt64,
toBigInt,
} from "../lib/utils.js";
import assert from "assert";
describe("Transaction builder", () => {
describe("setType", () => {
it("should assign type transfer", () => {
const tx = new TransactionBuilder().setType("transfer");
assert.strictEqual(tx.type, "transfer");
});
it("should assign type contract", () => {
const tx = new TransactionBuilder().setType("contract");
assert.strictEqual(tx.type, "contract");
});
it("should assign type data", () => {
const tx = new TransactionBuilder().setType("data");
assert.strictEqual(tx.type, "data");
});
it("should assign type token", () => {
const tx = new TransactionBuilder().setType("token");
assert.strictEqual(tx.type, "token");
});
it("should assign type hosting", () => {
const tx = new TransactionBuilder().setType("hosting");
assert.strictEqual(tx.type, "hosting");
});
it("should assign type code proposal", () => {
const tx = new TransactionBuilder().setType("code_proposal");
assert.strictEqual(tx.type, "code_proposal");
});
it("should assign type code approval", () => {
const tx = new TransactionBuilder().setType("code_approval");
assert.strictEqual(tx.type, "code_approval");
});
});
describe("setCode", () => {
it("should insert the code into the transaction data", () => {
const tx = new TransactionBuilder("transfer").setCode(
"my smart contract code"
);
assert.strictEqual(
new TextDecoder().decode(tx.data.code),
"my smart contract code"
);
});
});
describe("setContent", () => {
it("should insert the content into the transaction data", () => {
const tx = new TransactionBuilder("transfer").setContent(
"my super content"
);
assert.deepStrictEqual(
tx.data.content,
new TextEncoder().encode("my super content")
);
});
});
describe("addOwnership", () => {
it("should add an ownership with a secret and its authorized keys into the transaction data", () => {
const tx = new TransactionBuilder("transfer").addOwnership(
"00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88",
[
{
publicKey:
"0001b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646",
encryptedSecretKey:
"00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88",
},
{
publicKey:
"0001b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646",
encryptedSecretKey:
"00601fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88",
},
]
);
assert.deepStrictEqual(
tx.data.ownerships[0].secret,
hexToUint8Array(
"00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"
)
);
assert.deepStrictEqual(tx.data.ownerships[0].authorizedKeys, [
{
publicKey: hexToUint8Array(
"0001b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"
),
encryptedSecretKey: hexToUint8Array(
"00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"
),
},
]);
});
});
describe("addUCOTransfer", () => {
it("should add an uco transfer to the transaction data", () => {
const tx = new TransactionBuilder("transfer").addUCOTransfer(
"0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646",
toBigInt(10.03)
);
assert.strictEqual(tx.data.ledger.uco.transfers.length, 1);
assert.deepStrictEqual(
tx.data.ledger.uco.transfers[0].to,
hexToUint8Array(
"0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"
)
);
assert.strictEqual(
tx.data.ledger.uco.transfers[0].amount,
toBigInt(10.03)
);
});
});
describe("addTokenTransfer", () => {
it("should add an token transfer to the transaction data", () => {
const tx = new TransactionBuilder("transfer").addTokenTransfer(
"0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646",
toBigInt(10.03),
"0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"
);
assert.strictEqual(tx.data.ledger.token.transfers.length, 1);
assert.deepStrictEqual(
tx.data.ledger.token.transfers[0].to,
hexToUint8Array(
"0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"
)
);
assert.strictEqual(
tx.data.ledger.token.transfers[0].amount,
toBigInt(10.03)
);
assert.deepStrictEqual(
tx.data.ledger.token.transfers[0].token,
hexToUint8Array(
"0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"
)
);
});
});
describe("previousSignaturePayload", () => {
it("should generate binary encoding of the transaction before signing", () => {
const code = `
condition inherit: [
uco_transferred: 0.020
]
actions triggered by: transaction do
set_type transfer
add_uco_ledger to: "000056E763190B28B4CF9AAF3324CF379F27DE9EF7850209FB59AA002D71BA09788A", amount: 0.020
end
`;
const content =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sit amet leo egestas, lobortis lectus a, dignissim orci.";
const secret = "mysecret";
const tx = new TransactionBuilder("transfer")
.addOwnership(secret, [
{
publicKey:
"0001b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646",
encryptedSecretKey:
"00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88",
},
])
.addUCOTransfer(
"0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646",
toBigInt(0.202)
)
.addTokenTransfer(
"0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646",
toBigInt(100),
"0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"
)
.setCode(code)
.setContent(content)
.addRecipient(
"0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"
);
const keypair = deriveKeyPair("seed", 0);
const address = deriveAddress("seed", 1);
tx.address = address;
tx.previousPublicKey = keypair.publicKey;
const payload = tx.previousSignaturePayload();
const expected_binary = concatUint8Arrays([
//Version
encodeInt32(1),
tx.address,
Uint8Array.from([253]),
//Code size
encodeInt32(code.length),
new TextEncoder().encode(code),
//Content size
encodeInt32(content.length),
new TextEncoder().encode(content),
// Nb of byte to encode nb of ownerships
Uint8Array.from([1]),
//Nb of ownerships
Uint8Array.from([1]),
//Secret size
encodeInt32(secret.length),
new TextEncoder().encode(secret),
// Nb of byte to encode nb of authorized keys
Uint8Array.from([1]),
// Nb of authorized keys
Uint8Array.from([1]),
// Authorized keys encoding
concatUint8Arrays([
hexToUint8Array(
"0001b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"
),
hexToUint8Array(
"00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"
),
]),
// Nb of byte to encode nb of uco transfers
Uint8Array.from([1]),
// Nb of uco transfers
Uint8Array.from([1]),
concatUint8Arrays([
hexToUint8Array(
"0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"
),
encodeInt64(toBigInt(0.202)),
]),
// Nb of byte to encode nb of Token transfers
Uint8Array.from([1]),
// Nb of Token transfers
Uint8Array.from([1]),
concatUint8Arrays([
hexToUint8Array(
"0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"
),
hexToUint8Array(
"0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"
),
encodeInt64(toBigInt(100)),
Uint8Array.from([1]),
Uint8Array.from([0]),
]),
// Nb of byte to encode nb of recipients
Uint8Array.from([1]),
// Nb of recipients
Uint8Array.from([1]),
hexToUint8Array(
"0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"
),
]);
assert.deepStrictEqual(payload, expected_binary);
});
});
describe("setPreviousSignatureAndPreviousPublicKey", () => {
it("should set previous signature and previous public key in transaction builder", () => {
const examplePublicKey =
"0101044d91a0a1a7cf06a2902d3842f82d2791bcbf3ee6f6dc8de0f90e53e9991c3cb33684b7b9e66f26e7c9f5302f73c69897be5f301de9a63521a08ac4ef34c18728";
const exampleSignature =
"3044022009ed5124c35feb3449f4287eb5a885dec06f10491146bf73d44684f5a2ced8d7022049e1fb29fcd6e622a8cd2e120931ab038987edbdc44e7a9ec12e5a290599a97e";
const tx = new TransactionBuilder(
"transfer"
).setPreviousSignatureAndPreviousPublicKey(
exampleSignature,
examplePublicKey
);
assert.strictEqual(
Buffer.from(tx.previousPublicKey).toString("hex"),
examplePublicKey
);
assert.strictEqual(
Buffer.from(tx.previousSignature).toString("hex"),
exampleSignature
);
});
});
describe("setAddress", () => {
it("should set this.address in transaction builder", () => {
const exampleAddress =
"0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88";
const tx = new TransactionBuilder("transfer").setAddress(exampleAddress);
assert.deepStrictEqual(tx.address, hexToUint8Array(exampleAddress));
});
});
describe("build", () => {
it("should build the transaction and the related signature", () => {
const tx = new TransactionBuilder("transfer")
.addUCOTransfer(
"0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646",
10.0
)
.build("seed", 0, "ed25519", "sha256");
assert.deepStrictEqual(
tx.address,
hexToUint8Array(
"00001ff1733caa91336976ee7cef5aff6bb26c7682213b8e6770ab82272f966dac35"
)
);
assert.deepStrictEqual(
tx.previousPublicKey,
hexToUint8Array(
"000161d6cd8da68207bd01198909c139c130a3df3a8bd20f4bacb123c46354ccd52c"
)
);
assert.strictEqual(
verify(
tx.previousSignature,
tx.previousSignaturePayload(),
tx.previousPublicKey
),
true
);
});
});
describe("originSignaturePayload", () => {
it("should generate binary encoding of the transaction before signing", () => {
const secret = "mysecret";
const content =
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sit amet leo egestas, lobortis lectus a, dignissim orci.";
const code = `condition inherit: [
uco_transferred: 0.020
]
actions triggered by: transaction do
set_type transfer
add_uco_ledger to: "000056E763190B28B4CF9AAF3324CF379F27DE9EF7850209FB59AA002D71BA09788A", amount: 0.020
end
`;
const tx = new TransactionBuilder("transfer")
.addOwnership(secret, [
{
publicKey:
"0001b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646",
encryptedSecretKey:
"00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88",
},
{
publicKey:
"0001a1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646",
encryptedSecretKey:
"00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88",
},
])
.addUCOTransfer(
"0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646",
toBigInt(0.202)
)
.addTokenTransfer(
"0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646",
toBigInt(100),
"0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"
)
.setCode(code)
.setContent(content)
.addRecipient(
"0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"
)
.build("seed", 0, "P256");
const transactionKeyPair = deriveKeyPair("seed", 0, "P256");
const previousSig = sign(
tx.previousSignaturePayload(),
transactionKeyPair.privateKey
);
const payload = tx.originSignaturePayload();
const expected_binary = concatUint8Arrays([
//Version
encodeInt32(1),
tx.address,
Uint8Array.from([253]),
//Code size
encodeInt32(code.length),
new TextEncoder().encode(code),
//Content size
encodeInt32(content.length),
new TextEncoder().encode(content),
// Nb of byte to encode nb of ownerships
Uint8Array.from([1]),
//Nb ownerships
Uint8Array.from([1]),
//Secret size
encodeInt32(secret.length),
new TextEncoder().encode(secret),
// Nb of byte to encode nb of authorized key
Uint8Array.from([1]),
// Nb of authorized keys
Uint8Array.from([2]),
// Authorized keys encoding
concatUint8Arrays([
hexToUint8Array(
"0001a1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"
),
hexToUint8Array(
"00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"
),
hexToUint8Array(
"0001b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"
),
hexToUint8Array(
"00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"
),
]),
// Nb of byte to encode nb of uco transfers
Uint8Array.from([1]),
// Nb of uco transfers
Uint8Array.from([1]),
concatUint8Arrays([
hexToUint8Array(
"0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"
),
encodeInt64(toBigInt(0.202)),
]),
// Nb of byte to encode nb of Token transfers
Uint8Array.from([1]),
// Nb of Token transfers
Uint8Array.from([1]),
concatUint8Arrays([
hexToUint8Array(
"0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"
),
hexToUint8Array(
"0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646"
),
encodeInt64(toBigInt(100)),
Uint8Array.from([1]),
Uint8Array.from([0]),
]),
// Nb of byte to encode nb of recipients
Uint8Array.from([1]),
// Nb of recipients
Uint8Array.from([1]),
hexToUint8Array(
"0000501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88"
),
transactionKeyPair.publicKey,
Uint8Array.from([previousSig.length]),
previousSig,
]);
assert.deepStrictEqual(payload, expected_binary);
});
});
describe("originSign", () => {
it("should sign the transaction with a origin private key", () => {
const originKeypair = deriveKeyPair("origin_seed", 0);
const tx = new TransactionBuilder("transfer")
.build("seed", 0)
.originSign(originKeypair.privateKey);
assert.strictEqual(
verify(
tx.originSignature,
tx.originSignaturePayload(),
originKeypair.publicKey
),
true
);
});
});
describe("toJSON", () => {
it("should return a JSON from the transaction", () => {
const originKeypair = deriveKeyPair("origin_seed", 0);
const transactionKeyPair = deriveKeyPair("seed", 0);
const tx = new TransactionBuilder("transfer")
.addUCOTransfer(
"0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646",
toBigInt(0.2193)
)
.addOwnership(Uint8Array.from([0, 1, 2, 3, 4]), [
{
publicKey:
"0001b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646",
encryptedSecretKey:
"00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88",
},
])
.build("seed", 0)
.originSign(originKeypair.privateKey);
const parsedTx = JSON.parse(tx.toJSON());
const previousSig = sign(
tx.previousSignaturePayload(),
transactionKeyPair.privateKey
);
const originSig = sign(
tx.originSignaturePayload(),
originKeypair.privateKey
);
assert.strictEqual(
parsedTx.address,
uint8ArrayToHex(deriveAddress("seed", 1))
);
assert.strictEqual(parsedTx.type, "transfer");
assert.strictEqual(
parsedTx.previousPublicKey,
uint8ArrayToHex(transactionKeyPair.publicKey)
);
assert.strictEqual(
parsedTx.previousSignature,
uint8ArrayToHex(previousSig)
);
assert.strictEqual(parsedTx.originSignature, uint8ArrayToHex(originSig));
assert.strictEqual(
parsedTx.data.ownerships[0].secret,
uint8ArrayToHex(Uint8Array.from([0, 1, 2, 3, 4]))
);
assert.deepStrictEqual(parsedTx.data.ledger.uco.transfers[0], {
to: "0000b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646",
amount: toBigInt(0.2193),
});
assert.deepStrictEqual(parsedTx.data.ownerships[0].authorizedKeys, [
{
publicKey:
"0001b1d3750edb9381c96b1a975a55b5b4e4fb37bfab104c10b0b6c9a00433ec4646",
encryptedSecretKey:
"00501fa2db78bcf8ceca129e6139d7e38bf0d61eb905441056b9ebe6f1d1feaf88",
},
]);
});
});
});