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
99 changes: 99 additions & 0 deletions packages/evolution/test/KeyHash.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { Equal, FastCheck } from "effect"
import { describe, expect, it } from "vitest"

import * as Bytes from "../src/Bytes.js"
import * as KeyHash from "../src/KeyHash.js"
import * as PrivateKey from "../src/PrivateKey.js"
import * as VKey from "../src/VKey.js"

const SAMPLE_HASH_HEX = "9493315cd92eb5d8c4304e67b7e16ae36d61d34502694657811a2c8e"
const SAMPLE_HASH_BYTES = new Uint8Array([
0x94, 0x93, 0x31, 0x5c, 0xd9, 0x2e, 0xb5, 0xd8, 0xc4, 0x30, 0x4e, 0x67, 0xb7, 0xe1, 0x6a, 0xe3, 0x6d, 0x61, 0xd3,
0x45, 0x02, 0x69, 0x46, 0x57, 0x81, 0x1a, 0x2c, 0x8e
])

describe("KeyHash", () => {
describe("fromHex / toHex roundtrip", () => {
it("decodes from hex and re-encodes to the same hex", () => {
const kh = KeyHash.fromHex(SAMPLE_HASH_HEX)
expect(KeyHash.toHex(kh)).toBe(SAMPLE_HASH_HEX)
})

it("throws on wrong-length hex", () => {
expect(() => KeyHash.fromHex("deadbeef")).toThrow()
})
})

describe("fromBytes / toBytes roundtrip", () => {
it("decodes from bytes and re-encodes to identical bytes", () => {
const kh = KeyHash.fromBytes(SAMPLE_HASH_BYTES)
expect(KeyHash.toBytes(kh)).toStrictEqual(SAMPLE_HASH_BYTES)
})

it("hash field is 28 bytes", () => {
const kh = KeyHash.fromBytes(SAMPLE_HASH_BYTES)
expect(kh.hash.length).toBe(28)
})
})

describe("fromPrivateKey", () => {
it("produces a KeyHash with 28-byte hash from a normal private key", () => {
const privKey = PrivateKey.fromBytes(PrivateKey.generate())
const kh = KeyHash.fromPrivateKey(privKey)
expect(kh).toBeInstanceOf(KeyHash.KeyHash)
expect(kh.hash.length).toBe(28)
})

it("produces a KeyHash with 28-byte hash from an extended private key", () => {
const privKey = PrivateKey.fromBytes(PrivateKey.generateExtended())
const kh = KeyHash.fromPrivateKey(privKey)
expect(kh).toBeInstanceOf(KeyHash.KeyHash)
expect(kh.hash.length).toBe(28)
})
})

describe("fromVKey", () => {
it("produces the same hash as fromPrivateKey for the same key", () => {
const privKey = PrivateKey.fromBytes(PrivateKey.generate())
const vkey = VKey.fromPrivateKey(privKey)
const fromPk = KeyHash.fromPrivateKey(privKey)
const fromVk = KeyHash.fromVKey(vkey)
expect(Bytes.equals(fromPk.hash, fromVk.hash)).toBe(true)
})
})

describe("equality", () => {
it("two KeyHashes from the same hex are equal", () => {
const kh1 = KeyHash.fromHex(SAMPLE_HASH_HEX)
const kh2 = KeyHash.fromHex(SAMPLE_HASH_HEX)
expect(Equal.equals(kh1, kh2)).toBe(true)
})

it("two KeyHashes from different bytes are not equal", () => {
const kh1 = KeyHash.fromBytes(SAMPLE_HASH_BYTES)
const other = new Uint8Array(28).fill(0xff)
const kh2 = KeyHash.fromBytes(other)
expect(Equal.equals(kh1, kh2)).toBe(false)
})
})

describe("arbitrary / property-based", () => {
it("property: fromBytes(toBytes(kh)) equals original", () => {
FastCheck.assert(
FastCheck.property(KeyHash.arbitrary, (kh) => {
const bytes = KeyHash.toBytes(kh)
const kh2 = KeyHash.fromBytes(bytes)
expect(Equal.equals(kh, kh2)).toBe(true)
})
)
})

it("property: all generated KeyHashes have 28-byte hash", () => {
FastCheck.assert(
FastCheck.property(KeyHash.arbitrary, (kh) => {
expect(kh.hash.length).toBe(28)
})
)
})
})
})
147 changes: 147 additions & 0 deletions packages/evolution/test/ScriptHash.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import * as CML from "@dcspark/cardano-multiplatform-lib-nodejs"
import { Equal, FastCheck } from "effect"
import { describe, expect, it } from "vitest"

import * as NativeScripts from "../src/NativeScripts.js"
import * as PlutusV1 from "../src/PlutusV1.js"
import * as PlutusV2 from "../src/PlutusV2.js"
import * as PlutusV3 from "../src/PlutusV3.js"
import * as Script from "../src/Script.js"
import * as ScriptHash from "../src/ScriptHash.js"

const SAMPLE_HASH_HEX = "b0d07d45fe9514f80213f4020e5a61241458be626841cde717cb38a7"
const SAMPLE_HASH_BYTES = new Uint8Array([
0xb0, 0xd0, 0x7d, 0x45, 0xfe, 0x95, 0x14, 0xf8, 0x02, 0x13, 0xf4, 0x02, 0x0e, 0x5a, 0x61, 0x24, 0x14, 0x58, 0xbe,
0x62, 0x68, 0x41, 0xcd, 0xe7, 0x17, 0xcb, 0x38, 0xa7
])

const PLUTUS_BYTES = new Uint8Array([0x49, 0x48, 0x01, 0x00, 0x00, 0x22, 0x21, 0x20, 0x01, 0x01])

describe("ScriptHash", () => {
describe("fromHex / toHex roundtrip", () => {
it("decodes from hex and re-encodes to the same hex", () => {
const sh = ScriptHash.fromHex(SAMPLE_HASH_HEX)
expect(ScriptHash.toHex(sh)).toBe(SAMPLE_HASH_HEX)
})

it("throws on wrong-length hex", () => {
expect(() => ScriptHash.fromHex("deadbeef")).toThrow()
})
})

describe("fromBytes / toBytes roundtrip", () => {
it("decodes from bytes and re-encodes to identical bytes", () => {
const sh = ScriptHash.fromBytes(SAMPLE_HASH_BYTES)
expect(ScriptHash.toBytes(sh)).toStrictEqual(SAMPLE_HASH_BYTES)
})

it("hash field is 28 bytes", () => {
const sh = ScriptHash.fromBytes(SAMPLE_HASH_BYTES)
expect(sh.hash.length).toBe(28)
})
})

describe("fromScript – Plutus scripts", () => {
it("PlutusV1: produces a 28-byte hash", () => {
const script: Script.Script = new PlutusV1.PlutusV1({ bytes: PLUTUS_BYTES })
const sh = ScriptHash.fromScript(script)
expect(sh).toBeInstanceOf(ScriptHash.ScriptHash)
expect(sh.hash.length).toBe(28)
})

it("PlutusV2: produces a 28-byte hash", () => {
const script: Script.Script = new PlutusV2.PlutusV2({ bytes: PLUTUS_BYTES })
const sh = ScriptHash.fromScript(script)
expect(sh.hash.length).toBe(28)
})

it("PlutusV3: produces a 28-byte hash", () => {
const script: Script.Script = new PlutusV3.PlutusV3({ bytes: PLUTUS_BYTES })
const sh = ScriptHash.fromScript(script)
expect(sh.hash.length).toBe(28)
})

it("PlutusV1, V2, V3 hashes differ for same bytes (different language tags)", () => {
const v1 = ScriptHash.fromScript(new PlutusV1.PlutusV1({ bytes: PLUTUS_BYTES }))
const v2 = ScriptHash.fromScript(new PlutusV2.PlutusV2({ bytes: PLUTUS_BYTES }))
const v3 = ScriptHash.fromScript(new PlutusV3.PlutusV3({ bytes: PLUTUS_BYTES }))
expect(Equal.equals(v1, v2)).toBe(false)
expect(Equal.equals(v2, v3)).toBe(false)
})
})

describe("fromScript – NativeScript", () => {
it("produces a 28-byte hash", () => {
const ns = FastCheck.sample(NativeScripts.arbitrary, { seed: 1, numRuns: 1 })[0]
const script: Script.Script = ns
const sh = ScriptHash.fromScript(script)
expect(sh.hash.length).toBe(28)
})
})

describe("fromScript – CML parity (Plutus)", () => {
it("PlutusV1 hash matches CML", () => {
const script: Script.Script = new PlutusV1.PlutusV1({ bytes: PLUTUS_BYTES })
const evolutionHex = ScriptHash.toHex(ScriptHash.fromScript(script))

const cmlScript = CML.Script.new_plutus_v1(CML.PlutusV1Script.from_raw_bytes(PLUTUS_BYTES))
const cmlHex = cmlScript.hash().to_hex()

expect(evolutionHex).toBe(cmlHex)
})

it("PlutusV2 hash matches CML", () => {
const script: Script.Script = new PlutusV2.PlutusV2({ bytes: PLUTUS_BYTES })
const evolutionHex = ScriptHash.toHex(ScriptHash.fromScript(script))

const cmlScript = CML.Script.new_plutus_v2(CML.PlutusV2Script.from_raw_bytes(PLUTUS_BYTES))
const cmlHex = cmlScript.hash().to_hex()

expect(evolutionHex).toBe(cmlHex)
})

it("PlutusV3 hash matches CML", () => {
const script: Script.Script = new PlutusV3.PlutusV3({ bytes: PLUTUS_BYTES })
const evolutionHex = ScriptHash.toHex(ScriptHash.fromScript(script))

const cmlScript = CML.Script.new_plutus_v3(CML.PlutusV3Script.from_raw_bytes(PLUTUS_BYTES))
const cmlHex = cmlScript.hash().to_hex()

expect(evolutionHex).toBe(cmlHex)
})
})

describe("equality", () => {
it("two ScriptHashes from the same hex are equal", () => {
const sh1 = ScriptHash.fromHex(SAMPLE_HASH_HEX)
const sh2 = ScriptHash.fromHex(SAMPLE_HASH_HEX)
expect(Equal.equals(sh1, sh2)).toBe(true)
})

it("two ScriptHashes from different bytes are not equal", () => {
const sh1 = ScriptHash.fromBytes(SAMPLE_HASH_BYTES)
const sh2 = ScriptHash.fromBytes(new Uint8Array(28).fill(0x00))
expect(Equal.equals(sh1, sh2)).toBe(false)
})
})

describe("arbitrary / property-based", () => {
it("property: fromBytes(toBytes(sh)) equals original", () => {
FastCheck.assert(
FastCheck.property(ScriptHash.arbitrary, (sh) => {
const bytes = ScriptHash.toBytes(sh)
const sh2 = ScriptHash.fromBytes(bytes)
expect(Equal.equals(sh, sh2)).toBe(true)
})
)
})

it("property: all generated ScriptHashes have 28-byte hash", () => {
FastCheck.assert(
FastCheck.property(ScriptHash.arbitrary, (sh) => {
expect(sh.hash.length).toBe(28)
})
)
})
})
})