Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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;
}
18 changes: 18 additions & 0 deletions src/crypto/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,21 @@ 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
57 changes: 41 additions & 16 deletions src/transaction/transaction.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Transaction } from './transaction';
import { Address } from '../crypto/address';
import { Transaction, FLAG_NOT_SIGNED } from './transaction';
import { Address, AddressType } from '../crypto/address';

Check failure on line 2 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (22.x)

'AddressType' is defined but never used

Check failure on line 2 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (22.x)

'AddressType' is defined but never used. Allowed unused vars must match /^_/u

Check failure on line 2 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (24.x)

'AddressType' is defined but never used

Check failure on line 2 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (24.x)

'AddressType' is defined but never used. Allowed unused vars must match /^_/u
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
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';
import { PublicKey as Ed25519PublicKey } from '../crypto/ed25519/public_key';

Check failure on line 9 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (22.x)

'Ed25519PublicKey' is defined but never used

Check failure on line 9 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (22.x)

'Ed25519PublicKey' is defined but never used. Allowed unused vars must match /^_/u

Check failure on line 9 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (24.x)

'Ed25519PublicKey' is defined but never used

Check failure on line 9 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (24.x)

'Ed25519PublicKey' is defined but never used. Allowed unused vars must match /^_/u
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
import { Signature as Ed25519Signature } from '../crypto/ed25519/signature';

Check failure on line 10 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (22.x)

'Ed25519Signature' is defined but never used

Check failure on line 10 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (22.x)

'Ed25519Signature' is defined but never used. Allowed unused vars must match /^_/u

Check failure on line 10 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (24.x)

'Ed25519Signature' is defined but never used

Check failure on line 10 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (24.x)

'Ed25519Signature' is defined but never used. Allowed unused vars must match /^_/u
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed

describe('Transaction', () => {
describe('decode transfer transaction', () => {
Expand Down Expand Up @@ -44,11 +47,9 @@
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);

Check failure on line 50 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (22.x)

Forbidden non-null assertion

Check failure on line 50 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (24.x)

Forbidden non-null assertion
// 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);

Check failure on line 52 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (22.x)

Forbidden non-null assertion

Check failure on line 52 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (24.x)

Forbidden non-null assertion

// Transaction ID
expect(bytesToHex(tx.id())).toBe(expectedTxId);
Expand Down Expand Up @@ -98,11 +99,9 @@
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);

Check failure on line 102 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (22.x)

Forbidden non-null assertion

Check failure on line 102 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (24.x)

Forbidden non-null assertion
// 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);

Check failure on line 104 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (22.x)

Forbidden non-null assertion

Check failure on line 104 in src/transaction/transaction.test.ts

View workflow job for this annotation

GitHub Actions / Build, lint and test (24.x)

Forbidden non-null assertion

// Transaction ID
expect(bytesToHex(tx.id())).toBe(expectedTxId);
Expand Down Expand Up @@ -138,17 +137,43 @@
});
});

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