Skip to content
Open
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
12 changes: 6 additions & 6 deletions src/crypto/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,23 +95,23 @@ export class Address {
}

/** Encode the address to the writer. */
encode(writer: Writer): void {
encode(w: Writer): void {
if (this.isTreasuryAddress()) {
writer.writeUint8(AddressType.TREASURY);
w.writeUint8(AddressType.TREASURY);
} else {
writer.writeFixedBytes(this.rawBytes());
w.writeFixedBytes(this.rawBytes());
}
}

/** Decode an Address from the reader. */
static decode(reader: Reader): Address {
const addrType = reader.readUint8();
static decode(r: Reader): Address {
const addrType = r.readUint8();

if (addrType === AddressType.TREASURY) {
return new Address(AddressType.TREASURY, new Uint8Array(20));
}

const data = reader.readFixedBytes(20);
const data = r.readFixedBytes(20);

return new Address(addrType as AddressType, data);
}
Expand Down
55 changes: 55 additions & 0 deletions src/crypto/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import type { Writer } from '../encoding';

/**
* Interface for a public key that can verify signatures.
*
* Concrete implementations: ed25519.PublicKey, bls.PublicKey
*/
export interface IPublicKey {
/** Raw key material. */
rawBytes(): Uint8Array;

/** Bech32m-encoded string representation (e.g. "public1..."). */
string(): string;

/** Encode the key to the writer. */
encode(w: Writer): void;

/** Verify a signature against a message. */
verify(msg: Uint8Array, sig: ISignature): boolean;
}

/**
* Interface for a cryptographic signature.
*
* Concrete implementations: ed25519.Signature, bls.Signature
*/
export interface ISignature {
/** Raw signature bytes. */
rawBytes(): Uint8Array;

/** Hex-encoded string representation. */
string(): string;

/** Encode the signature to the writer. */
encode(w: Writer): void;
}

/**
* Interface for a private key that can sign messages.
*
* Concrete implementations: ed25519.PrivateKey, bls.PrivateKey
*/
export interface IPrivateKey {
/** Raw scalar bytes. */
rawBytes(): Uint8Array;

/** Bech32m-encoded string representation (e.g. "secret1..."). */
string(): string;

/** Derive the corresponding public key. */
publicKey(): IPublicKey;

/** Sign a message and return the signature. */
sign(msg: Uint8Array): ISignature;
}
15 changes: 15 additions & 0 deletions src/crypto/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,18 @@ export function hexToBytes(hex: string): Uint8Array {

return bytes;
}

/**
* Decode a bech32m-encoded public key string into raw bytes.
*
* Validates the HRP against the given value (default "public" for mainnet).
*/
export function decodeBech32PublicKey(text: string, expectedHrp: string = 'public'): Uint8Array {
const { hrp, data } = decodeWithType(text);

if (hrp !== expectedHrp) {
throw new Error(`Invalid public key hrp: ${hrp}, expected: ${expectedHrp}`);
}

return data;
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

export * from './wallet';
export * from './transaction/transaction';
export * from './transaction';
export * from './transaction/payload';
export { Address } from './crypto/address';
export { Amount } from './types/amount';
Expand Down
2 changes: 2 additions & 0 deletions src/transaction/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './transaction';
export * from './payload';
2 changes: 1 addition & 1 deletion src/transaction/payload/_payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export enum PayloadType {
}

export interface Payload {
encode(writer: Writer): void;
encode(w: Writer): void;
getType(): PayloadType;
signer(): Address;
}
Expand Down
26 changes: 13 additions & 13 deletions src/transaction/payload/bond.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ export class BondPayload {
public readonly stake: Amount
) {}

encode(writer: Writer): void {
this.sender.encode(writer);
this.receiver.encode(writer);
encode(w: Writer): void {
this.sender.encode(w);
this.receiver.encode(w);

if (this.publicKey === null) {
writer.writeVarInt(0);
w.writeVarInt(0);
} else {
writer.writeVarInt(96);
writer.writeFixedBytes(this.publicKey);
w.writeVarInt(96);
w.writeFixedBytes(this.publicKey);
}

this.stake.encode(writer);
this.stake.encode(w);
}

getType(): PayloadType {
Expand All @@ -34,19 +34,19 @@ export class BondPayload {
return this.sender;
}

static decode(reader: Reader): BondPayload {
const sender = Address.decode(reader);
const receiver = Address.decode(reader);
const pubKeySize = reader.readVarInt();
static decode(r: Reader): BondPayload {
const sender = Address.decode(r);
const receiver = Address.decode(r);
const pubKeySize = r.readVarInt();
let publicKey: Uint8Array | null = null;

if (pubKeySize === 96n) {
publicKey = reader.readFixedBytes(96);
publicKey = r.readFixedBytes(96);
} else if (pubKeySize !== 0n) {
throw new Error(`invalid public key size: ${pubKeySize}`);
}

const stake = Amount.decode(reader);
const stake = Amount.decode(r);

return new BondPayload(sender, receiver, publicKey, stake);
}
Expand Down
12 changes: 6 additions & 6 deletions src/transaction/payload/sortition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ export class SortitionPayload {
public readonly proof: Uint8Array
) {}

encode(writer: Writer): void {
this.address.encode(writer);
writer.writeFixedBytes(this.proof);
encode(w: Writer): void {
this.address.encode(w);
w.writeFixedBytes(this.proof);
}

getType(): PayloadType {
Expand All @@ -22,9 +22,9 @@ export class SortitionPayload {
return this.address;
}

static decode(reader: Reader): SortitionPayload {
const address = Address.decode(reader);
const proof = reader.readFixedBytes(48);
static decode(r: Reader): SortitionPayload {
const address = Address.decode(r);
const proof = r.readFixedBytes(48);

return new SortitionPayload(address, proof);
}
Expand Down
18 changes: 9 additions & 9 deletions src/transaction/payload/transfer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Address } from '../../crypto/address';
import { Amount } from '../../types/amount';
import type { Writer, Reader } from '../../encoding';
import type { Writer as w, Reader } from '../../encoding';

import { PayloadType } from './_payload';

Expand All @@ -11,10 +11,10 @@ export class TransferPayload {
public readonly amount: Amount
) {}

encode(writer: Writer): void {
this.sender.encode(writer);
this.receiver.encode(writer);
this.amount.encode(writer);
encode(w: w): void {
this.sender.encode(w);
this.receiver.encode(w);
this.amount.encode(w);
}

getType(): PayloadType {
Expand All @@ -25,10 +25,10 @@ export class TransferPayload {
return this.sender;
}

static decode(reader: Reader): TransferPayload {
const sender = Address.decode(reader);
const receiver = Address.decode(reader);
const amount = Amount.decode(reader);
static decode(r: Reader): TransferPayload {
const sender = Address.decode(r);
const receiver = Address.decode(r);
const amount = Amount.decode(r);

return new TransferPayload(sender, receiver, amount);
}
Expand Down
10 changes: 5 additions & 5 deletions src/transaction/payload/unbond.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Address } from '../../crypto/address';
import type { Writer, Reader } from '../../encoding';
import type { Writer as w, Reader } from '../../encoding';

import { PayloadType } from './_payload';

export class UnbondPayload {
constructor(public readonly validator: Address) {}

encode(writer: Writer): void {
this.validator.encode(writer);
encode(w: w): void {
this.validator.encode(w);
}

getType(): PayloadType {
Expand All @@ -18,8 +18,8 @@ export class UnbondPayload {
return this.validator;
}

static decode(reader: Reader): UnbondPayload {
const validator = Address.decode(reader);
static decode(r: Reader): UnbondPayload {
const validator = Address.decode(r);

return new UnbondPayload(validator);
}
Expand Down
16 changes: 8 additions & 8 deletions src/transaction/payload/withdraw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ export class WithdrawPayload {
public readonly amount: Amount
) {}

encode(writer: Writer): void {
this.fromAddr.encode(writer);
this.toAddr.encode(writer);
this.amount.encode(writer);
encode(w: Writer): void {
this.fromAddr.encode(w);
this.toAddr.encode(w);
this.amount.encode(w);
}

getType(): PayloadType {
Expand All @@ -25,10 +25,10 @@ export class WithdrawPayload {
return this.fromAddr;
}

static decode(reader: Reader): WithdrawPayload {
const fromAddr = Address.decode(reader);
const toAddr = Address.decode(reader);
const amount = Amount.decode(reader);
static decode(r: Reader): WithdrawPayload {
const fromAddr = Address.decode(r);
const toAddr = Address.decode(r);
const amount = Amount.decode(r);

return new WithdrawPayload(fromAddr, toAddr, amount);
}
Expand Down
53 changes: 38 additions & 15 deletions src/transaction/transaction.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Transaction } from './transaction';
import { Transaction, FLAG_NOT_SIGNED } from './transaction';
import { Address } from '../crypto/address';
import { Amount } from '../types/amount';
import { Height } from '../types/height';
import { Reader } from '../encoding';
import { Reader, Writer } from '../encoding';
import type { TransferPayload, BondPayload } from './payload';
import { PayloadType } from './payload';
import { PrivateKey as Ed25519PrivateKey } from '../crypto/ed25519/private_key';

describe('Transaction', () => {
describe('decode transfer transaction', () => {
Expand Down Expand Up @@ -44,11 +45,9 @@ describe('Transaction', () => {
expect(tx.publicKey).not.toBeNull();

// Signature is 64 bytes (ED25519_ACCOUNT)
const sig = tx.signature as Uint8Array;
expect(sig.length).toBe(64);
expect(tx.signature?.rawBytes().length).toBe(64);
// Public key is 32 bytes (ED25519_ACCOUNT)
const pub = tx.publicKey as Uint8Array;
expect(pub.length).toBe(32);
expect(tx.publicKey?.rawBytes().length).toBe(32);

// Transaction ID
expect(bytesToHex(tx.id())).toBe(expectedTxId);
Expand Down Expand Up @@ -98,11 +97,9 @@ describe('Transaction', () => {
expect(tx.publicKey).not.toBeNull();

// Signature is 64 bytes (ED25519_ACCOUNT)
const sig = tx.signature as Uint8Array;
expect(sig.length).toBe(64);
expect(tx.signature?.rawBytes().length).toBe(64);
// Public key is 32 bytes (ED25519_ACCOUNT)
const pub = tx.publicKey as Uint8Array;
expect(pub.length).toBe(32);
expect(tx.publicKey?.rawBytes().length).toBe(32);

// Transaction ID
expect(bytesToHex(tx.id())).toBe(expectedTxId);
Expand Down Expand Up @@ -138,17 +135,43 @@ describe('Transaction', () => {
});
});

describe('sign', () => {
it('should throw error when sign is called', () => {
describe('sign and verify', () => {
it('should sign and verify a transaction with round-trip', () => {
const privKey = Ed25519PrivateKey.random();
const pubKey = privKey.publicKey();
const sender = pubKey.accountAddress();
const receiver = Address.fromString('pc1r0g22ufzn8qtw0742dmfglnw73e260hep0k3yra');
const lockTime = new Height(0);
const sender = Address.fromString('pc1z5x2a0lkt5nrrdqe0rkcv6r4pfkmdhrr3mawvua');
const receiver = Address.fromString('pc1zt6qcdymkk48c5ds0fzfsaf6puwu8w8djn3ffpn');
const amount = Amount.zero();
const fee = Amount.zero();

const tx = Transaction.createTransferTx(lockTime, sender, receiver, amount, fee);

expect(() => tx.sign()).toThrow('signing is not supported in the TypeScript SDK');
// Unsigned tx should not have sig/pubKey
expect(tx.signature).toBeNull();
expect(tx.publicKey).toBeNull();

// Mark as unsigned and sign
tx.flags = FLAG_NOT_SIGNED;
tx.sign(privKey);

expect(tx.signature).not.toBeNull();
expect(tx.publicKey).not.toBeNull();
expect(tx.verify()).toBe(true);

// Encode signed tx
const w = new Writer();

tx.encode(w);
const signedBytes = w.toBytes();

// Decode round-trip
const decoded = Transaction.decode(new Reader(signedBytes));

expect(decoded.flags).toBe(0);
expect(decoded.signature).not.toBeNull();
expect(decoded.publicKey).not.toBeNull();
expect(decoded.verify()).toBe(true);
});
});
});
Expand Down
Loading
Loading