Compare commits

..

No commits in common. "d0d79d2f07b29a54ae6629ea660c372dc447119b" and "15617f699837ade99ecdc6ab1c7148e80265ac79" have entirely different histories.

13 changed files with 23 additions and 589 deletions

View File

@ -1,66 +0,0 @@
# Legacy Digest Functions (`lyng.legacy_digest`)
> ⚠️ **Security warning:** The functions in this module use cryptographically broken
> algorithms. Do **not** use them for passwords, digital signatures, integrity
> verification against adversarial tampering, or any other security-sensitive
> purpose. They exist solely for compatibility with legacy protocols and file
> formats that require specific hash values.
Import when you need to produce a SHA-1 digest for an existing protocol or format:
```lyng
import lyng.legacy_digest
```
## `LegacyDigest` Object
### `sha1(data): String`
Computes the SHA-1 digest of `data` and returns it as a 40-character lowercase
hex string.
`data` can be:
| Type | Behaviour |
|----------|----------------------------------------|
| `String` | Encoded as UTF-8, then hashed |
| `Buffer` | Raw bytes hashed directly |
| anything | Falls back to `toString()` then UTF-8 |
```lyng
import lyng.legacy_digest
// String input
val h = LegacyDigest.sha1("abc")
assertEquals("a9993e364706816aba3e25717850c26c9cd0d89d", h)
// Empty string
assertEquals("da39a3ee5e6b4b0d3255bfef95601890afd80709", LegacyDigest.sha1(""))
```
```lyng
import lyng.legacy_digest
import lyng.buffer
// Buffer input (raw bytes)
val buf = Buffer.decodeHex("616263") // 0x61 0x62 0x63 = "abc"
assertEquals("a9993e364706816aba3e25717850c26c9cd0d89d", LegacyDigest.sha1(buf))
```
## Implementation Notes
- Pure Kotlin/KMP — no native libraries or extra dependencies.
- Follows FIPS 180-4.
- The output is always lowercase hex, never uppercase or binary.
## When to Use
Use `lyng.legacy_digest` only when an external system you cannot change requires
a SHA-1 value, for example:
- old git-style content addresses
- some OAuth 1.0 / HMAC-SHA1 signature schemes
- legacy file checksums defined in published specs
For any new design choose a current hash function (SHA-256 or better) once
Lyng adds a `lyng.digest` module.

View File

@ -75,9 +75,6 @@ Sources: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt`, `lynglib/s
- `Matrix`, `Vector`, `matrix(rows)`, `vector(values)`, dense linear algebra, inversion, solving, and matrix slicing with `m[row, col]`.
- `import lyng.buffer`
- `Buffer`, `MutableBuffer`.
- `import lyng.legacy_digest`
- `LegacyDigest.sha1(data): String` — SHA-1 hex digest; `data` may be `String` (UTF-8) or `Buffer` (raw bytes).
- ⚠️ Cryptographically broken. Use only for legacy protocol / file-format compatibility.
- `import lyng.serialization`
- `Lynon` serialization utilities.
- `import lyng.time`

View File

@ -164,31 +164,6 @@ println(z.exp())
See [Complex](Complex.md).
### Legacy Digest Module (`lyng.legacy_digest`)
For situations where an external protocol or file format requires a SHA-1 value,
Lyng now ships a `lyng.legacy_digest` module backed by a pure Kotlin/KMP
implementation with no extra dependencies.
> ⚠️ SHA-1 is cryptographically broken. Use only for legacy-compatibility work.
```lyng
import lyng.legacy_digest
val hex = LegacyDigest.sha1("abc")
// → "a9993e364706816aba3e25717850c26c9cd0d89d"
// Also accepts raw bytes:
import lyng.buffer
val buf = Buffer.decodeHex("616263")
assertEquals(hex, LegacyDigest.sha1(buf))
```
The name `LegacyDigest` is intentional: it signals that these algorithms belong
to a compatibility layer, not to a current security toolkit.
See [LegacyDigest](LegacyDigest.md).
### Binary Operator Interop Registry
Lyng now provides a general mechanism for mixed binary operators through `lyng.operators`.

View File

@ -31,7 +31,6 @@ sealed class CodeContext {
var typeParamDecls: List<TypeDecl.TypeParam> = emptyList()
val pendingInitializations = mutableMapOf<String, Pos>()
val declaredMembers = mutableSetOf<String>()
val declaredMethodNames = mutableSetOf<String>()
val classScopeMembers = mutableSetOf<String>()
val memberOverrides = mutableMapOf<String, Boolean>()
val memberFieldIds = mutableMapOf<String, Int>()

View File

@ -456,7 +456,7 @@ class Compiler(
}
}
private fun predeclareClassMembers(target: MutableSet<String>, overrides: MutableMap<String, Boolean>, methodNames: MutableSet<String>? = null) {
private fun predeclareClassMembers(target: MutableSet<String>, overrides: MutableMap<String, Boolean>) {
val saved = cc.savePos()
var depth = 0
val modifiers = setOf(
@ -478,22 +478,18 @@ class Compiler(
Token.Type.RBRACE -> if (depth == 0) break else depth--
Token.Type.ID -> if (depth == 0) {
var sawOverride = false
var sawStatic = false
while (t.type == Token.Type.ID && t.value in modifiers) {
if (t.value == "override") sawOverride = true
if (t.value == "static") sawStatic = true
t = nextNonWs()
}
when (t.value) {
"fun", "fn", "val", "var" -> {
val isMethod = t.value == "fun" || t.value == "fn"
val nameToken = nextNonWs()
if (nameToken.type == Token.Type.ID) {
val afterName = cc.peekNextNonWhitespace()
if (afterName.type != Token.Type.DOT) {
target.add(nameToken.value)
overrides[nameToken.value] = sawOverride
if (isMethod && !sawStatic) methodNames?.add(nameToken.value)
}
}
}
@ -2787,7 +2783,7 @@ class Compiler(
Token.Type.LPAREN -> {
cc.next()
if (shouldTreatAsClassScopeCall(left, next.value)) {
val parsed = parseArgs(null, implicitItTypeForMemberLambda(left, next.value))
val parsed = parseArgs(null, implicitItTypeNameForMemberLambda(left, next.value))
val args = parsed.first
val tailBlock = parsed.second
isCall = true
@ -2804,7 +2800,7 @@ class Compiler(
val receiverType = if (next.value == "apply" || next.value == "run") {
inferReceiverTypeFromRef(left)
} else null
val parsed = parseArgs(receiverType, implicitItTypeForMemberLambda(left, next.value))
val parsed = parseArgs(receiverType, implicitItTypeNameForMemberLambda(left, next.value))
val args = parsed.first
val tailBlock = parsed.second
if (left is LocalVarRef && left.name == "scope") {
@ -2883,7 +2879,7 @@ class Compiler(
val receiverType = if (next.value == "apply" || next.value == "run") {
inferReceiverTypeFromRef(left)
} else null
val itType = implicitItTypeForMemberLambda(left, next.value)
val itType = implicitItTypeNameForMemberLambda(left, next.value)
val lambda = parseLambdaExpression(receiverType, implicitItType = itType)
val argPos = next.pos
val args = listOf(ParsedArgument(ExpressionStatement(lambda, argPos), next.pos))
@ -3291,7 +3287,7 @@ class Compiler(
private suspend fun parseLambdaExpression(
expectedReceiverType: String? = null,
wrapAsExtensionCallable: Boolean = false,
implicitItType: TypeDecl? = null
implicitItType: String? = null
): ObjRef {
// lambda args are different:
val startPos = cc.currentPos()
@ -3308,15 +3304,14 @@ class Compiler(
val slotParamNames = if (hasImplicitIt) paramNames + "it" else paramNames
val paramSlotPlan = buildParamSlotPlan(slotParamNames)
if (implicitItType != null) {
val cls = resolveTypeDeclObjClass(implicitItType)
val cls = resolveClassByName(implicitItType)
?: resolveTypeDeclObjClass(TypeDecl.Simple(implicitItType, false))
val itSlot = paramSlotPlan.slots["it"]?.index
if (itSlot != null) {
if (cls != null) {
if (cls != null && itSlot != null) {
val paramTypeMap = slotTypeByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() }
paramTypeMap[itSlot] = cls
}
val paramTypeDeclMap = slotTypeDeclByScopeId.getOrPut(paramSlotPlan.id) { mutableMapOf() }
paramTypeDeclMap[itSlot] = implicitItType
paramTypeDeclMap[itSlot] = TypeDecl.Simple(implicitItType, false)
}
}
@ -4750,13 +4745,6 @@ class Compiler(
return null
}
private fun classMethodReturnTypeDecl(typeName: String?, name: String): TypeDecl? {
if (typeName == null) return null
classMethodReturnTypeDeclByName[typeName]?.get(name)?.let { return it }
classMethodReturnTypeByName[typeName]?.get(name)?.let { return TypeDecl.Simple(it.className, false) }
return null
}
private fun classMethodReturnClass(targetClass: ObjClass?, name: String): ObjClass? {
if (targetClass == null) return null
if (targetClass == ObjDynamic.type) return ObjDynamic.type
@ -4870,26 +4858,6 @@ class Compiler(
classMethodReturnTypeDecl(targetClass, "getAt")
}
is MethodCallRef -> methodReturnTypeDeclByRef[ref] ?: inferMethodCallReturnTypeDecl(ref)
is ImplicitThisMethodCallRef -> {
val typeName = ref.preferredThisTypeName() ?: currentImplicitThisTypeName()
val receiverDecl = typeName?.let { TypeDecl.Simple(it, false) }
inferMethodCallReturnTypeDecl(ref.methodName(), receiverDecl, ref.arguments())
?: classMethodReturnTypeDecl(typeName, ref.methodName())
?: typeName?.let { resolveClassByName(it) }?.let { classMethodReturnTypeDecl(it, ref.methodName()) }
}
is ThisMethodSlotCallRef -> {
val typeName = currentImplicitThisTypeName()
val receiverDecl = typeName?.let { TypeDecl.Simple(it, false) }
inferMethodCallReturnTypeDecl(ref.methodName(), receiverDecl, ref.arguments())
?: classMethodReturnTypeDecl(typeName, ref.methodName())
?: typeName?.let { resolveClassByName(it) }?.let { classMethodReturnTypeDecl(it, ref.methodName()) }
}
is QualifiedThisMethodSlotCallRef -> {
val receiverDecl = TypeDecl.Simple(ref.receiverTypeName(), false)
inferMethodCallReturnTypeDecl(ref.methodName(), receiverDecl, ref.arguments())
?: classMethodReturnTypeDecl(ref.receiverTypeName(), ref.methodName())
?: resolveClassByName(ref.receiverTypeName())?.let { classMethodReturnTypeDecl(it, ref.methodName()) }
}
is CallRef -> callReturnTypeDeclByRef[ref] ?: inferCallReturnTypeDecl(ref)
is BinaryOpRef -> inferBinaryOpReturnTypeDecl(ref)
is StatementRef -> (ref.statement as? ExpressionStatement)?.let { resolveReceiverTypeDecl(it.ref) }
@ -5154,7 +5122,6 @@ class Compiler(
private fun inferMethodCallReturnTypeDecl(ref: MethodCallRef): TypeDecl? {
methodReturnTypeDeclByRef[ref]?.let { return it }
val inferred = inferMethodCallReturnTypeDecl(ref.name, resolveReceiverTypeDecl(ref.receiver), ref.args)
?: classMethodReturnTypeDecl(resolveReceiverClassForMember(ref.receiver), ref.name)
if (inferred != null) {
methodReturnTypeDeclByRef[ref] = inferred
}
@ -5305,17 +5272,21 @@ class Compiler(
}
}
private fun implicitItTypeForMemberLambda(receiver: ObjRef, memberName: String): TypeDecl? {
private fun implicitItTypeNameForMemberLambda(receiver: ObjRef, memberName: String): String? {
if (memberName == "fill" && isListTypeRef(receiver)) {
return TypeDecl.Simple("Int", false)
return "Int"
}
if (memberName == "let" || memberName == "also") {
val receiverType = inferTypeDeclFromRef(receiver) ?: resolveReceiverTypeDecl(receiver)
return receiverType?.let { makeTypeDeclNonNullable(it) }
return inferReceiverTypeFromRef(receiver)
}
return when (memberName) {
val typeDecl = when (memberName) {
"forEach", "map" -> inferIterableElementTypeDecl(receiver)
else -> null
} ?: return null
return when (typeDecl) {
is TypeDecl.Simple -> typeDecl.name.substringAfterLast('.')
is TypeDecl.Generic -> typeDecl.name.substringAfterLast('.')
else -> resolveTypeDeclObjClass(typeDecl)?.className
}
}
@ -6322,7 +6293,7 @@ class Compiler(
*/
private suspend fun parseArgs(
expectedTailBlockReceiver: String? = null,
implicitItType: TypeDecl? = null
implicitItType: String? = null
): Pair<List<ParsedArgument>, Boolean> {
val args = mutableListOf<ParsedArgument>()
@ -7761,7 +7732,7 @@ class Compiler(
classCtx?.let { ctx ->
val callableMembers = classScopeCallableMembersByClassName.getOrPut(qualifiedName) { mutableSetOf() }
predeclareClassScopeMembers(qualifiedName, ctx.classScopeMembers, callableMembers)
predeclareClassMembers(ctx.declaredMembers, ctx.memberOverrides, ctx.declaredMethodNames)
predeclareClassMembers(ctx.declaredMembers, ctx.memberOverrides)
val existingExternInfo = if (isExtern) resolveCompileClassInfo(qualifiedName) else null
if (existingExternInfo != null) {
ctx.memberFieldIds.putAll(existingExternInfo.fieldIds)
@ -7810,13 +7781,6 @@ class Compiler(
ctx.memberFieldIds[param.name] = ctx.nextFieldId++
}
}
// Pre-assign method IDs for all declared methods so forward
// references within the class body resolve correctly.
for (method in ctx.declaredMethodNames) {
if (!ctx.memberMethodIds.containsKey(method)) {
ctx.memberMethodIds[method] = ctx.nextMethodId++
}
}
compileClassInfos[qualifiedName] = CompileClassInfo(
name = qualifiedName,
packageName = packageName,

View File

@ -1,124 +0,0 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sergeych.lyng
/**
* Pure Kotlin/KMP implementation of legacy hash functions.
*
* SHA-1 is cryptographically broken and must not be used for security-sensitive
* purposes (password hashing, digital signatures, etc.). It is retained here
* solely for compatibility with legacy protocols and file formats that require it.
*/
internal object LegacyDigest {
/**
* Compute the SHA-1 digest of [input] and return it as a lowercase hex string.
*
* SHA-1 is **cryptographically insecure**. Use only for protocol compatibility.
*/
fun sha1Hex(input: ByteArray): String {
val digest = sha1(input)
return buildString(40) {
for (b in digest) {
val v = b.toInt() and 0xFF
if (v < 16) append('0')
append(v.toString(16))
}
}
}
private fun sha1(input: ByteArray): ByteArray {
// Initial hash values
var h0 = 0x67452301
var h1 = 0xEFCDAB89.toInt()
var h2 = 0x98BADCFE.toInt()
var h3 = 0x10325476
var h4 = 0xC3D2E1F0.toInt()
// Pre-processing: pad message to a multiple of 512 bits (64 bytes).
// Append 0x80, then zeros, then the 64-bit big-endian bit-length.
val msgLen = input.size
val bitLen = msgLen.toLong() * 8L
// Minimum padding: 1 byte (0x80) + 8 bytes (length) = 9 bytes.
// Total length must be ≡ 0 (mod 64).
val padded = run {
val totalLen = ((msgLen + 1 + 8 + 63) / 64) * 64
ByteArray(totalLen).also { buf ->
input.copyInto(buf)
buf[msgLen] = 0x80.toByte()
// Big-endian 64-bit bit-length in the last 8 bytes
for (i in 0..7) {
buf[totalLen - 8 + i] = ((bitLen ushr (56 - i * 8)) and 0xFF).toByte()
}
}
}
val w = IntArray(80)
var blockStart = 0
while (blockStart < padded.size) {
// Build the 80-word message schedule
for (i in 0..15) {
val off = blockStart + i * 4
w[i] = ((padded[off].toInt() and 0xFF) shl 24) or
((padded[off + 1].toInt() and 0xFF) shl 16) or
((padded[off + 2].toInt() and 0xFF) shl 8) or
(padded[off + 3].toInt() and 0xFF)
}
for (i in 16..79) {
val x = w[i - 3] xor w[i - 8] xor w[i - 14] xor w[i - 16]
w[i] = (x shl 1) or (x ushr 31) // ROTL-1
}
var a = h0; var b = h1; var c = h2; var d = h3; var e = h4
for (t in 0..19) {
val f = (b and c) or (b.inv() and d)
val temp = ((a shl 5) or (a ushr 27)) + f + e + 0x5A827999 + w[t]
e = d; d = c; c = (b shl 30) or (b ushr 2); b = a; a = temp
}
for (t in 20..39) {
val f = b xor c xor d
val temp = ((a shl 5) or (a ushr 27)) + f + e + 0x6ED9EBA1 + w[t]
e = d; d = c; c = (b shl 30) or (b ushr 2); b = a; a = temp
}
for (t in 40..59) {
val f = (b and c) or (b and d) or (c and d)
val temp = ((a shl 5) or (a ushr 27)) + f + e + 0x8F1BBCDC.toInt() + w[t]
e = d; d = c; c = (b shl 30) or (b ushr 2); b = a; a = temp
}
for (t in 60..79) {
val f = b xor c xor d
val temp = ((a shl 5) or (a ushr 27)) + f + e + 0xCA62C1D6.toInt() + w[t]
e = d; d = c; c = (b shl 30) or (b ushr 2); b = a; a = temp
}
h0 += a; h1 += b; h2 += c; h3 += d; h4 += e
blockStart += 64
}
return ByteArray(20).also { out ->
fun putInt(off: Int, v: Int) {
out[off] = (v ushr 24).toByte()
out[off + 1] = (v ushr 16).toByte()
out[off + 2] = (v ushr 8).toByte()
out[off + 3] = v.toByte()
}
putInt(0, h0); putInt(4, h1); putInt(8, h2); putInt(12, h3); putInt(16, h4)
}
}
}

View File

@ -29,7 +29,6 @@ import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportManager
import net.sergeych.lyng.stdlib_included.complexLyng
import net.sergeych.lyng.stdlib_included.decimalLyng
import net.sergeych.lyng.stdlib_included.legacyDigestLyng
import net.sergeych.lyng.stdlib_included.matrixLyng
import net.sergeych.lyng.stdlib_included.observableLyng
import net.sergeych.lyng.stdlib_included.operatorsLyng
@ -897,19 +896,6 @@ class Script(
module.eval(Source("lyng.complex", complexLyng))
ObjComplexSupport.bindTo(module)
}
addPackage("lyng.legacy_digest") { module ->
module.eval(Source("lyng.legacy_digest", legacyDigestLyng))
module.bindObject("LegacyDigest") {
addFun("sha1") {
val data = requiredArg<Obj>(0)
val bytes = when (data) {
is ObjBuffer -> data.byteArray.toByteArray()
else -> data.toString().encodeToByteArray()
}
ObjString(LegacyDigest.sha1Hex(bytes))
}
}
}
addPackage("lyng.buffer") {
it.addConstDoc(
name = "Buffer",

View File

@ -926,16 +926,11 @@ open class ObjClass(
candidate.methodId
} else null
}
val id = methodId ?: inherited ?: methodIdMap[name]?.let { it } ?: run {
methodId ?: inherited ?: methodIdMap[name]?.let { it } ?: run {
methodIdMap[name] = nextMethodId
nextMethodId++
methodIdMap[name]!!
}
// Register the resolved ID so subsequent assignMethodId calls (e.g. for static
// methods) don't reuse the same numeric slot for a different member.
methodIdMap[name] = id
if (id >= nextMethodId) nextMethodId = id + 1
id
} else {
methodId
}

View File

@ -1102,119 +1102,4 @@ class OOTest {
""".trimIndent())
}
@Test
fun testExtendingObjectWithExternals2() = runTest {
val s = EvalSession()
s.eval("""
import lyng.serialization
object Storage {
extern val spaceUsed: Int
extern val spaceAvailable: Int
/*
Return packed binary data or null
*/
extern fun getPacked(key: String): Buffer?
/*
Upsert packed binary data
*/
extern fun putPacked(key: String,value: Buffer)
/*
Delete data.
@return true if data were actually deleted, false means
there were no data for the key.
*/
extern fun delete(key: String): Bool
override fun putAt(key: String,value: Object) {
putPacked(key, Lynon.encode(value).toBuffer())
}
override fun getAt(key: String): Object? =
getPacked(key)?.let { Lynon.decode(it.toBitInput()) }
}
""".trimIndent()
)
val scope = s.getScope() as ModuleScope
scope.bindObject("Storage") {
init { _ ->
data = mutableMapOf<String, ObjBuffer>()
}
addVal("spaceUsed") {
val storage = (thisObj as ObjInstance).data as MutableMap<String, ObjBuffer>
ObjInt(storage.values.sumOf { it.size }.toLong())
}
addVal("spaceAvailable") {
val storage = (thisObj as ObjInstance).data as MutableMap<String, ObjBuffer>
val capacity = 1_024
ObjInt((capacity - storage.values.sumOf { it.size }).toLong())
}
addFun("getPacked") {
val storage = (thisObj as ObjInstance).data as MutableMap<String, ObjBuffer>
val key = (args.list[0] as ObjString).value
storage[key] ?: ObjNull
}
addFun("putPacked") {
val storage = (thisObj as ObjInstance).data as MutableMap<String, ObjBuffer>
val key = (args.list[0] as ObjString).value
val value = args.list[1] as ObjBuffer
storage[key] = value
ObjVoid
}
addFun("delete") {
val storage = (thisObj as ObjInstance).data as MutableMap<String, ObjBuffer>
val key = (args.list[0] as ObjString).value
ObjBool(storage.remove(key) != null)
}
}
s.eval("""
assertEquals(0, Storage.spaceUsed)
assertEquals(1024, Storage.spaceAvailable)
val missing: String? = Storage["missing"]
assertEquals(null, missing)
Storage["name"] = "alice"
Storage["count"] = 42
val name: String? = Storage["name"]
val count: Int? = Storage["count"]
assertEquals("alice", name)
assertEquals(42, count)
assert(Storage.spaceUsed > 0)
assert(Storage.spaceAvailable < 1024)
val wrappedName: String? = Storage.getAt("name")
assertEquals("alice", wrappedName)
Storage.putAt("flag", true)
val flag: Bool? = Storage["flag"]
assertEquals(true, flag)
assert(Storage.delete("name"))
val deletedName: String? = Storage["name"]
assertEquals(null, deletedName)
assert(!Storage.delete("name"))
""".trimIndent())
}
@Test
fun testForwardSymbolsUsageMustBeAllowed() = runTest {
eval("""
class Foo(x) {
fn fn2() {
fn1()
println("fn2")
}
fn fn1() {
println("fn1")
}
}
val foo = Foo(33)
foo.fn2()
""".trimIndent())
}
}

View File

@ -109,19 +109,6 @@ class ScriptTest {
assertTrue(res is ObjString && res.value.isNotEmpty())
}
@Test
fun testNullableLetKeepsReceiverMemberType() = runTest {
Script.newScope().eval(
"""
import lyng.serialization
val packed: Buffer? = Lynon.encode("alice").toBuffer()
val decoded = packed?.let { Lynon.decode(it.toBitInput()) }
assertEquals("alice", decoded)
""".trimIndent()
)
}
@Test
fun testNoInfiniteRecursionOnUnknownInNestedClosure() = runTest {
val scope = Script.newScope()

View File

@ -111,22 +111,4 @@ class ComplexModuleTest {
)
}
@Test
fun testOperatorSlotDispatch() = runTest {
val scope = Script.newScope()
scope.eval(
"""
import lyng.complex
val a = Complex(1.0, 2.0)
val b = Complex(3.0, -1.0)
val product = a * b
assertEquals(5.0, product.re)
assertEquals(5.0, product.im)
val sum = a + Complex(0.0, 0.0)
assertEquals(1.0, sum.re)
assertEquals(2.0, sum.im)
""".trimIndent()
)
}
}

View File

@ -1,113 +0,0 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
class LegacyDigestTest {
// --- Kotlin-level unit tests for the SHA-1 implementation ---
@Test
fun sha1KotlinEmptyString() {
// SHA-1("") = da39a3ee5e6b4b0d3255bfef95601890afd80709
assertEquals(
"da39a3ee5e6b4b0d3255bfef95601890afd80709",
LegacyDigest.sha1Hex(ByteArray(0))
)
}
@Test
fun sha1KotlinAbc() {
// SHA-1("abc") = a9993e364706816aba3e25717850c26c9cd0d89d
assertEquals(
"a9993e364706816aba3e25717850c26c9cd0d89d",
LegacyDigest.sha1Hex("abc".encodeToByteArray())
)
}
@Test
fun sha1KotlinLongerMessage() {
// SHA-1("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq")
// = 84983e441c3bd26ebaae4aa1f95129e5e54670f1
val msg = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
assertEquals(
"84983e441c3bd26ebaae4aa1f95129e5e54670f1",
LegacyDigest.sha1Hex(msg.encodeToByteArray())
)
}
@Test
fun sha1KotlinExactlyOneBlock() {
// "The quick brown fox jumps over the lazy dog"
// SHA-1 = 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12
val msg = "The quick brown fox jumps over the lazy dog"
assertEquals(
"2fd4e1c67a2d28fced849ee1bb76e7391b93eb12",
LegacyDigest.sha1Hex(msg.encodeToByteArray())
)
}
// --- Lyng-level integration tests ---
@Test
fun sha1LyngStringInput() = runTest {
eval(
"""
import lyng.legacy_digest
assertEquals(
"a9993e364706816aba3e25717850c26c9cd0d89d",
LegacyDigest.sha1("abc")
)
assertEquals(
"da39a3ee5e6b4b0d3255bfef95601890afd80709",
LegacyDigest.sha1("")
)
""".trimIndent()
)
}
@Test
fun sha1LyngBufferInput() = runTest {
eval(
"""
import lyng.legacy_digest
import lyng.buffer
val buf = Buffer.decodeHex("616263") // "abc" in hex
assertEquals(
"a9993e364706816aba3e25717850c26c9cd0d89d",
LegacyDigest.sha1(buf)
)
""".trimIndent()
)
}
@Test
fun sha1LyngReturnType() = runTest {
eval(
"""
import lyng.legacy_digest
val h = LegacyDigest.sha1("hello")
assert(h is String)
assertEquals(40, h.length)
""".trimIndent()
)
}
}

View File

@ -1,33 +0,0 @@
package lyng.legacy_digest
/*
Legacy cryptographic digest functions.
⚠️ WARNING: The functions in this module use algorithms that are
CRYPTOGRAPHICALLY BROKEN and must NOT be used for any security-sensitive
purpose, including:
- Password hashing
- Digital signatures or MACs
- Integrity verification against adversarial tampering
Use only for interoperability with legacy protocols, file formats, or
external systems that require these specific hash values.
For secure hashing, use SHA-256 or better (not yet available in this stdlib).
*/
extern object LegacyDigest {
/*
Compute the SHA-1 digest of `data` and return it as a lowercase hex string.
`data` may be a String (hashed as UTF-8) or a Buffer (hashed as raw bytes).
⚠️ SHA-1 is cryptographically broken. Use only for protocol compatibility.
Example:
import lyng.legacy_digest
val digest = LegacyDigest.sha1("hello world")
// "2aae6c69a64d7c8b96eacbbe0ced5df20f3a8e89" (note: known SHA-1 of "hello world")
*/
extern fun sha1(data): String
}