Merge branch 'voice'

This commit is contained in:
Kae 2023-07-20 15:00:59 +10:00
commit c1ae238086
101 changed files with 6356 additions and 156 deletions

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "source/extern/opus"]
path = source/extern/opus
url = https://github.com/xiph/opus

View File

@ -1,7 +1,8 @@
{ {
"opensb": { "opensb": {
"groups": { "groups": {
"camera": { "name": "Camera" } "camera": { "name": "Camera" },
"voice": { "name": "Voice" }
}, },
"name": "Open^#ebd74a;Starbound", "name": "Open^#ebd74a;Starbound",
"binds": { "binds": {
@ -21,13 +22,10 @@
"group" : "camera", "group" : "camera",
"name": "Zoom Out" "name": "Zoom Out"
}, },
"test": { "pushToTalk": {
"default": [{ "default": [],
"type": "key", "group" : "voice",
"value": "C", "name": "Push To Talk"
"mods": ["LShift"]
}],
"name": "Test Bind"
} }
} }
} }

View File

@ -1,4 +1,6 @@
{ {
"universeScriptContexts" : { "OpenStarbound" : ["/scripts/opensb/universeclient/universeclient.lua"] },
// Disables scissoring and letterboxing on vanilla and modded warp cinematics // Disables scissoring and letterboxing on vanilla and modded warp cinematics
"warpCinematicBase" : { "warpCinematicBase" : {
"scissor" : false, "scissor" : false,

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 447 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

View File

@ -0,0 +1,174 @@
{
"scripts" : ["/interface/opensb/voicechat/voicechat.lua"],
"scriptDelta" : 1,
"scriptWidgetCallbacks" : [
"selectDevice",
"voiceToggle",
"switchVoiceMode"
],
"canvasClickCallbacks" : {
"inputVolume" : "inputVolume",
"voiceVolume" : "voiceVolume",
"threshold" : "threshold"
},
"gui" : {
"panefeature" : {
"type" : "panefeature",
"positionLocked" : false
},
"background" : {
"type" : "background",
"fileHeader" : "/interface/opensb/voicechat/header.png",
"fileBody" : "/interface/opensb/voicechat/body.png",
"fileFooter" : "/interface/opensb/voicechat/footer.png"
},
"voiceVolumeLabel" : {
"type" : "label",
"value" : "THEIR VOLUME",
"position" : [26, 178],
"wrapWidth" : 48,
"lineSpacing" : 0.75,
"hAnchor" : "mid",
"vAnchor" : "mid"
},
"voiceVolume" : {
"type" : "canvas",
"rect" : [50, 171, 247, 186],
"captureMouseEvents" : true,
"captureKeyboardEvents" : false
},
"inputVolumeLabel" : {
"type" : "label",
"value" : "YOUR VOLUME",
"position" : [26, 158],
"wrapWidth" : 48,
"lineSpacing" : 0.75,
"hAnchor" : "mid",
"vAnchor" : "mid"
},
"inputVolume" : {
"type" : "canvas",
"rect" : [50, 151, 247, 166],
"captureMouseEvents" : true,
"captureKeyboardEvents" : false
},
"enableVoiceToggleBack" : {
"type" : "image",
"file" : "/interface/opensb/voicechat/bigbuttonback.png?multiply=0f0",
"position" : [2, 189],
"zlevel" : -1
},
"enableVoiceToggle" : {
"type" : "button",
"pressedOffset" : [0, 0],
"position" : [2, 189],
"base" : "/interface/opensb/voicechat/bigbutton.png?replace;fff=fff0;000=0007",
"hover" : "/interface/opensb/voicechat/bigbutton.png?replace;fff=fff7;000=3337",
"press" : "/interface/opensb/voicechat/bigbutton.png?replace;fff=000;000=7777",
"callback" : "voiceToggle",
"fontSize" : 16,
"zlevel" : 1
},
"voiceModeLabel" : {
"type" : "label",
"value" : "VOICE MODE",
"position" : [26, 133],
"wrapWidth" : 32,
"lineSpacing" : 0.75,
"hAnchor" : "mid",
"vAnchor" : "mid"
},
"pushToTalkBack" : {
"type" : "image",
"file" : "/interface/opensb/voicechat/pushtotalkback.png?multiply=0f0",
"position" : [50, 121],
"zlevel" : -1
},
"pushToTalk" : {
"type" : "button",
"pressedOffset" : [0, 0],
"position" : [50, 121],
"base" : "/interface/opensb/voicechat/pushtotalk.png?replace;fff=fff0;000=0007",
"hover" : "/interface/opensb/voicechat/pushtotalk.png?replace;fff=fff7;000=3337",
"press" : "/interface/opensb/voicechat/pushtotalk.png?replace;fff=000;000=7777",
"callback" : "switchVoiceMode",
"fontSize" : 16,
"zlevel" : 1
},
"voiceActivityBack" : {
"type" : "image",
"file" : "/interface/opensb/voicechat/activityback.png?multiply=0f0",
"position" : [167, 121],
"zlevel" : -1
},
"voiceActivity" : {
"type" : "button",
"pressedOffset" : [0, 0],
"position" : [167, 121],
"base" : "/interface/opensb/voicechat/activity.png?replace;fff=fff0;000=0007",
"hover" : "/interface/opensb/voicechat/activity.png?replace;fff=fff7;000=3337",
"press" : "/interface/opensb/voicechat/activity.png?replace;fff=000;000=7777",
"callback" : "switchVoiceMode",
"fontSize" : 16,
"zlevel" : 1
},
"thresholdLevel" : {
"type" : "label",
"value" : "THRESHOLD",
"position" : [26, 109],
"wrapWidth" : 48,
"lineSpacing" : 0.75,
"hAnchor" : "mid",
"vAnchor" : "mid"
},
"threshold" : {
"type" : "canvas",
"rect" : [50, 102, 247, 117],
"captureMouseEvents" : true,
"captureKeyboardEvents" : false
},
"devices" : {
"type" : "scrollArea",
"rect" : [3, 16, 248, 98],
"children" : {
"list" : {
"type" : "list",
"schema" : {
"selectedBG" : "/interface/opensb/voicechat/deviceback.png?multiply=0f0",
"unselectedBG" : "/interface/opensb/voicechat/deviceback.png?multiply=222",
"spacing" : [0, 1],
"memberSize" : [234, 16],
"listTemplate" : {
"background" : {
"type" : "image",
"file" : "/interface/opensb/voicechat/deviceback.png?multiply=222",
"position" : [0, 0],
"zlevel" : -1
},
"button" : {
"type" : "button",
"callback" : "selectDevice",
"caption" : "Unnamed",
"base" : "/interface/opensb/voicechat/device.png?replace;fff=fff0;000=0007",
"hover" : "/interface/opensb/voicechat/device.png?replace;fff=fff7;000=3337",
"press" : "/interface/opensb/voicechat/device.png?replace;fff=000;000=7777",
"pressedOffset" : [0, 0],
"position" : [0, 0]
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,228 @@
--constants
local PATH = "/interface/opensb/voicechat/"
local DEVICE_LIST_WIDGET = "devices.list"
local DEFAULT_DEVICE_NAME = "Use System Default"
local NULL_DEVICE_NAME = "No Audio Device"
local COLD_COLOR = {25, 255, 255, 225}
local HOT_COLOR = {255, 96, 96, 225}
local MINIMUM_DB = -80
local VOICE_MAX, INPUT_MAX = 1.75, 1.0
local MID = 7.5
local fmt = string.format
local debugging = false
local function log(...)
if not debugging then return end
sb.logInfo(...)
end
local function mapToRange(x, min, max)
return math.min(1, math.max(0, (x - min)) / max)
end
local function linear(a, b, c)
return a + (b - a) * c
end
local settings = {}
local function set(k, v)
settings[k] = v
local newSettings = jobject()
newSettings[k] = v
voice.mergeSettings(newSettings)
return v
end
local devicesToWidgets = {}
local widgetsToDevices = {}
local nullWidget
local function addDeviceToList(deviceName)
local name = widget.addListItem(DEVICE_LIST_WIDGET)
widget.setText(fmt("%s.%s.button", DEVICE_LIST_WIDGET, name), deviceName)
widgetsToDevices[name] = deviceName
devicesToWidgets[deviceName] = name
log("Added audio device '%s' to list", deviceName)
return name
end
function selectDevice()
local selected = widget.getListSelected(DEVICE_LIST_WIDGET)
if selected == nullWidget then
set("inputEnabled", false)
widget.setListSelected(DEVICE_LIST_WIDGET, nullWidget)
end
local deviceName = widgetsToDevices[selected]
if deviceName == DEFAULT_DEVICE_NAME then deviceName = nil end
if settings.deviceName == deviceName then
local inputEnabled = set("inputEnabled", not settings.inputEnabled)
widget.setListSelected(DEVICE_LIST_WIDGET, inputEnabled and selected or nullWidget)
else
set("deviceName", deviceName)
set("inputEnabled", true)
end
end
local function initCallbacks()
widget.registerMemberCallback(DEVICE_LIST_WIDGET, "selectDevice", selectDevice)
end
local function updateVoiceButton()
local enabled = settings.enabled
widget.setText("enableVoiceToggle", enabled and "^#0f0;disable voice chat" or "^#f00;enable voice chat")
widget.setImage("enableVoiceToggleBack", PATH .. "bigbuttonback.png?multiply=" .. (enabled and "0f0" or "f00"))
end
local function updateVoiceModeButtons()
local pushToTalk = settings.inputMode:lower() == "pushtotalk"
widget.setImage("pushToTalkBack", PATH .. "pushtotalkback.png?multiply=" .. (pushToTalk and "0f0" or "f00"))
widget.setImage("voiceActivityBack", PATH .. "activityback.png?multiply=" .. (pushToTalk and "f00" or "0f0"))
widget.setText("pushToTalk", pushToTalk and "^#0f0;PUSH TO TALK" or "^#f00;PUSH TO TALK")
widget.setText("voiceActivity", pushToTalk and "^#f00;ACTIVITY" or "^#0f0;ACTIVITY")
end
local voiceCanvas, inputCanvas = nil, nil
local function updateVolumeCanvas(canvas, volume, multiplier)
canvas:clear()
local lineEnd = 1 + volume * 195
local lineColor = {95, 110, 255, 225}
local multiplied = volume * multiplier
if multiplied > 1 then
local level = (multiplied - 1) / (multiplier - 1)
for i = 1, 4 do
lineColor[i] = linear(lineColor[i], HOT_COLOR[i], level)
end
else
for i = 1, 4 do
lineColor[i] = linear(lineColor[i], COLD_COLOR[i], 1 - multiplied)
end
end
canvas:drawLine({1, MID}, {lineEnd, MID}, lineColor, 60)
canvas:drawLine({lineEnd - 0.5, MID}, {lineEnd + 0.5, MID}, {255, 255, 255, 200}, 60)
local str = volume == 0 and "^#f00,shadow;MUTED" or fmt("^shadow;%s%%", math.floor(volume * multiplier * 1000) * 0.1)
canvas:drawText(str, {position = {92.5, MID}, horizontalAnchor = "mid", verticalAnchor = "mid"}, 16, {255, 255, 255, 255})
end
local thresholdCanvas = nil
local function updateThresholdCanvas(canvas, dB)
canvas:clear()
local scale = pane.scale()
local lineEnd = 1 + (1 - (dB / MINIMUM_DB)) * 195
local lineColor = {255, 255, 0, 127}
canvas:drawLine({1, 2}, {lineEnd, 2}, lineColor, scale)
canvas:drawLine({1, 13}, {lineEnd, 13}, lineColor, scale)
lineColor[4] = 64
canvas:drawLine({lineEnd, 2}, {196, 2}, lineColor, scale)
canvas:drawLine({lineEnd, 13}, {196, 13}, lineColor, scale)
canvas:drawLine({lineEnd - 0.5, MID}, {lineEnd + 0.5, MID}, {255, 255, 255, 200}, 60)
local loudness = 1 - (voice.speaker().smoothDecibels / MINIMUM_DB)
local loudnessEnd = math.min(1 + loudness * 195, 196)
if loudnessEnd > 0 then
lineColor[4] = 200
canvas:drawLine({1, MID}, {loudnessEnd, MID}, lineColor, 4 * scale)
end
local str = fmt("^shadow;%sdB", math.floor(dB * 10) * 0.1)
canvas:drawText(str, {position = {92.5, MID}, horizontalAnchor = "mid", verticalAnchor = "mid"}, 16, {255, 255, 255, 255})
end
function init()
settings = voice.getSettings()
voiceCanvas = widget.bindCanvas("voiceVolume")
inputCanvas = widget.bindCanvas("inputVolume")
thresholdCanvas = widget.bindCanvas("threshold")
end
function displayed()
devicesToWidgets = {}
widgetsToDevices = {}
widget.clearListItems(DEVICE_LIST_WIDGET)
initCallbacks()
addDeviceToList(DEFAULT_DEVICE_NAME)
for i, v in pairs(voice.devices()) do
addDeviceToList(v)
end
nullWidget = widget.addListItem(DEVICE_LIST_WIDGET)
local nullWidgetPath = fmt("%s.%s", DEVICE_LIST_WIDGET, nullWidget)
widget.setPosition(nullWidgetPath, {0, 10000})
widget.setVisible(nullWidgetPath, false)
local preferredDeviceWidget = devicesToWidgets[settings.deviceName or DEFAULT_DEVICE_NAME]
if preferredDeviceWidget and settings.inputEnabled then
widget.setListSelected(DEVICE_LIST_WIDGET, preferredDeviceWidget)
end
updateVoiceButton()
updateVoiceModeButtons()
updateVolumeCanvas(voiceCanvas, settings.outputVolume / VOICE_MAX, VOICE_MAX)
updateVolumeCanvas(inputCanvas, settings.inputVolume / INPUT_MAX, INPUT_MAX)
updateThresholdCanvas(thresholdCanvas, settings.threshold)
end
function update()
updateThresholdCanvas(thresholdCanvas, settings.threshold)
end
local function sliderToValue(x)
return mapToRange(x, 5, 187)
end
local function mouseInSlider(mouse)
return mouse[1] > 0 and mouse[1] < 197
and mouse[2] > 0 and mouse[2] < 16
end
local function handleVolume(canvas, mouse, multiplier, setter)
if not mouseInSlider(mouse) then return end
local volumePreMul = sliderToValue(mouse[1])
local volume = volumePreMul * multiplier;
if math.abs(volume - 1) < 0.01 then
volumePreMul = 1 / multiplier
volume = 1
end
updateVolumeCanvas(canvas, volumePreMul, multiplier)
setter(volume)
end
function voiceVolume(mouse, button)
if button ~= 0 then return end
handleVolume(voiceCanvas, mouse, VOICE_MAX, function(v) set("outputVolume", v) end)
end
function inputVolume(mouse, button)
if button ~= 0 then return end
handleVolume(inputCanvas, mouse, INPUT_MAX, function(v) set("inputVolume", v) end)
end
function threshold(mouse, button)
if button ~= 0 then return end
if not mouseInSlider(mouse) then return end
local dB = (1 - sliderToValue(mouse[1])) * MINIMUM_DB
set("threshold", dB)
updateThresholdCanvas(thresholdCanvas, dB)
end
function switchVoiceMode(mode)
log("switching voice mode to %s", tostring(mode))
local success, err = pcall(function()
set("inputMode", mode)
updateVoiceModeButtons()
end)
if not success then log("%s", err) end
end
function voiceToggle()
set("enabled", not settings.enabled)
widget.playSound(fmt("/sfx/interface/voice_%s.ogg", settings.enabled and "on" or "off"), 0)
updateVoiceButton()
end

Binary file not shown.

After

Width:  |  Height:  |  Size: 1017 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

View File

@ -1,25 +1,48 @@
{ {
"paneLayout" : { "paneLayout" : {
"voiceLabel" : {
"type" : "label",
"position" : [119, 185],
"hAnchor" : "mid",
"value" : "VOICE"
},
"showVoiceSettings" : {
"type" : "button",
"position" : [30, 169],
"caption" : "Settings",
"base" : "/interface/optionsmenu/duocontrolsbutton.png",
"hover" : "/interface/optionsmenu/duocontrolsbuttonhover.png"
},
"showVoicePlayers" : {
"type" : "button",
"disabled" : true,
"position" : [133, 169],
"caption" : "^#a0a000,font=iosevka-semiboldoblique;TODO^#aa7;:^reset; Players",
"base" : "/interface/optionsmenu/duocontrolsbutton.png",
"hover" : "/interface/optionsmenu/duocontrolsbuttonhover.png"
},
"showKeybindings" : { "showKeybindings" : {
"type" : "button", "type" : "button",
"position" : [150, 95], "position" : [153, 95],
"caption" : "Game Binds", "caption" : "Game Binds",
"base" : "/interface/optionsmenu/controlsbutton.png", "base" : "/interface/optionsmenu/tricontrolsbutton.png",
"hover" : "/interface/optionsmenu/controlsbuttonhover.png" "hover" : "/interface/optionsmenu/tricontrolsbuttonhover.png"
}, },
"showModBindings" : { "showModBindings" : {
"type" : "button", "type" : "button",
"position" : [87, 95], "position" : [87, 95],
"caption" : "Mod Binds", "caption" : "Mod Binds",
"base" : "/interface/optionsmenu/controlsbutton.png", "base" : "/interface/optionsmenu/tricontrolsbutton.png",
"hover" : "/interface/optionsmenu/controlsbuttonhover.png" "hover" : "/interface/optionsmenu/tricontrolsbuttonhover.png"
}, },
"showGraphics" : { "showGraphics" : {
"type" : "button", "type" : "button",
"position" : [24, 95], "position" : [21, 95],
"caption" : "Graphics", "caption" : "Graphics",
"base" : "/interface/optionsmenu/controlsbutton.png", "base" : "/interface/optionsmenu/tricontrolsbutton.png",
"hover" : "/interface/optionsmenu/controlsbuttonhover.png" "hover" : "/interface/optionsmenu/tricontrolsbuttonhover.png"
} },
"sfxValueLabel" : { "position" : [192, 142] }, // this is 2px too low in vanilla lol
"musicSlider" : { "position" : [62, 126] }
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 213 B

After

Width:  |  Height:  |  Size: 213 B

View File

Before

Width:  |  Height:  |  Size: 213 B

After

Width:  |  Height:  |  Size: 213 B

View File

@ -1,4 +1,5 @@
{ {
"genericScriptContexts" : { "OpenStarbound" : "/scripts/opensb/player/player.lua" },
"wireConfig" : { "wireConfig" : {
"innerBrightnessScale" : 20, "innerBrightnessScale" : 20,
"firstStripeThickness" : 0.6, "firstStripeThickness" : 0.6,

View File

@ -0,0 +1,30 @@
local module = {}
modules.commands = module
local commands = {}
local function command(name, func)
commands[name] = func
end
function module.init()
for name, func in pairs(commands) do
message.setHandler("/" .. name, function(isLocal, _, ...)
if not isLocal then
return
else
return func(...)
end
end)
end
end
command("run", function(src)
local success, result = pcall(loadstring, src, "/run")
if not success then
return "^#f00;compile error: " .. result
else
local success, result = pcall(result)
return not success and "^#f00;error: " .. result or sb.printJson(result)
end
end)

View File

@ -0,0 +1,2 @@
require "/scripts/opensb/util/modules.lua"
modules("/scripts/opensb/player/", {"commands"})

View File

@ -0,0 +1,2 @@
require "/scripts/opensb/util/modules.lua"
modules("/scripts/opensb/universeclient/", {"voicemanager"})

View File

@ -0,0 +1,168 @@
-- Manages the voice HUD indicators and click-to-mute/unmute.
local fmt = string.format
local sqrt, min, max = math.sqrt, math.min, math.max
local module = {}
modules.voice_manager = module
--constants
local INDICATOR_PATH = "/interface/opensb/voicechat/indicator/"
local BACK_INDICATOR_IMAGE = INDICATOR_PATH .. "back.png"
local FRONT_INDICATOR_IMAGE = INDICATOR_PATH .. "front.png"
local FRONT_MUTED_INDICATOR_IMAGE = INDICATOR_PATH .. "front_muted.png"
local INDICATOR_SIZE = {300, 48}
local LINE_PADDING = 12
local LINE_WIDTH = 296
local LINE_WIDTH_PADDED = LINE_WIDTH - LINE_PADDING
local LINE_COLOR = {50, 210, 255, 255}
local FONT_DIRECTIVES = "?border=1;333;3337?border=1;333;3330"
local NAME_PREFIX = "^noshadow,white,set;"
local function dbToLoudness(db) return 2 ^ (db / 8) end
local canvas
local linePaddingDrawable
do
local drawable = { image = BACK_INDICATOR_IMAGE, position = {0, 0}, color = LINE_COLOR, centered = false }
function linePaddingDrawable(a, b)
drawable.image = BACK_INDICATOR_IMAGE .. fmt("?crop=%i;%i;%i;%i?fade=fff;1", a, 0, b, INDICATOR_SIZE[2])
drawable.position[1] = a
return drawable;
end
end
local lineDrawable = { line = {{LINE_PADDING, 24}, {10, 24}}, width = 48, color = LINE_COLOR }
local function line(pos, value)
local width = math.floor((LINE_WIDTH * value) + 0.5)
LINE_COLOR[4] = 255 * math.min(1, sqrt(width / 350))
if width > 0 then
canvas:drawDrawable(linePaddingDrawable(0, math.min(12, width)), pos)
if width > 12 then
lineDrawable.line[2][1] = math.min(width, LINE_WIDTH_PADDED)
canvas:drawDrawable(lineDrawable, pos)
if width > LINE_WIDTH_PADDED then
canvas:drawDrawable(linePaddingDrawable(LINE_WIDTH_PADDED, width), pos)
end
end
end
end
local drawable = { image = BACK_INDICATOR_IMAGE, centered = false }
local textPositioning = { position = {0, 0}, horizontalAnchor = "left", verticalAnchor = "mid" }
local hoveredSpeaker = nil
local hoveredSpeakerIndex = 1
local hoveredSpeakerPosition = {0, 0}
local function mouseOverSpeaker(mouse, pos, expand)
expand = tonumber(expand) or 0
return (mouse[1] > pos[1] - expand and mouse[1] < pos[1] + 300 + expand)
and (mouse[2] > pos[2] - expand and mouse[2] < pos[2] + 48 + expand)
end
local function drawSpeakerBar(mouse, pos, speaker, i)
drawable.image = BACK_INDICATOR_IMAGE
canvas:drawDrawable(drawable, pos)
line(pos, dbToLoudness(speaker.smoothDecibels))
local hovering = not speaker.isLocal and mouseOverSpeaker(mouse, pos)
textPositioning.position = {pos[1] + 49, pos[2] + 24}
textPositioning.horizontalAnchor = "left"
local text = NAME_PREFIX ..
(hovering and (speaker.muted and "^#31d2f7;Unmute^reset; " or "^#f43030;Mute^reset; ") or "")
.. speaker.name
canvas:drawText(text, textPositioning, 16, nil, nil, nil, FONT_DIRECTIVES)
drawable.image = speaker.muted and FRONT_MUTED_INDICATOR_IMAGE or FRONT_INDICATOR_IMAGE
canvas:drawDrawable(drawable, pos)
if hovering then
hoveredSpeaker = speaker
hoveredSpeakerIndex = i
hoveredSpeakerPosition = pos
if input.keyHeld("LShift") then
textPositioning.position = {pos[1] + 288, pos[2] + 24}
textPositioning.horizontalAnchor = "right"
canvas:drawText("^#fff7,font=iosevka-semibold;" .. tostring(speaker.speakerId), textPositioning, 16, nil, nil, nil, FONT_DIRECTIVES)
end
if input.mouseDown("MouseLeft") then
local muted = not voice.speakerMuted(speaker.speakerId)
interface.queueMessage((muted and "^#f43030;Muted^reset; " or "^#31d2f7;Unmuted^reset; ") .. speaker.name, 4, 0.5)
voice.setSpeakerMuted(speaker.speakerId, muted)
speaker.muted = muted
end
end
end
local function simulateSpeakers()
local speakers = {}
for i = 2, 5 + math.floor((math.sin(os.clock()) * 4) + .5) do
local dB = -48 + 48 * math.sin(os.clock() + (i * 0.5))
speakers[i] = {
speakerId = i,
entityId = -65536 * i,
name = "Player " .. i,
decibels = dB,
smoothDecibels = dB,
muted = false
}
end
return speakers
end
local function drawIndicators()
canvas:clear()
local screenSize = canvas:size()
local mousePosition = canvas:mousePosition()
local basePos = {screenSize[1] - 350, 50}
local speakersRemaining, speakers = {}, {}
local hoveredSpeakerId = nil
if hoveredSpeaker then
if not mouseOverSpeaker(mousePosition, hoveredSpeakerPosition, 16) then
hoveredSpeaker = nil
else
hoveredSpeakerId = hoveredSpeaker.speakerId
end
end
local speakerCount = 0
for i, speaker in pairs(voice.speakers()) do
local speakerId = speaker.speakerId
speakersRemaining[speakerId] = true
if speakerId == hoveredSpeakerId then
hoveredSpeaker = speaker
else
speakerCount = speakerCount + 1
speakers[speakerCount] = speaker
end
end
if hoveredSpeaker then
local len = #speakers
if hoveredSpeakerIndex > len then
for i = len + 1, hoveredSpeakerIndex - 1 do
speakers[i] = false
end
speakers[hoveredSpeakerIndex] = hoveredSpeaker
else
table.insert(speakers, hoveredSpeakerIndex, hoveredSpeaker)
end
end
for i, v in pairs(speakers) do
if v then
local pos = {basePos[1], basePos[2] + (i - 1) * 52}
drawSpeakerBar(mousePosition, pos, v, i)
end
end
end
function module.init()
canvas = interface.bindCanvas("voice", true)
end
function module.update()
drawIndicators()
end

View File

@ -0,0 +1,31 @@
modules = setmetatable({}, {__call = function(this, path, names)
for i, name in pairs(names) do
require(path .. name .. ".lua")
end
end})
local modules, type = modules, type
local function call(func, ...)
if type(func) == "function" then
return func(...)
end
end
function init(...)
script.setUpdateDelta(1)
for i, module in pairs(modules) do
call(module.init, ...)
end
end
function update(...)
for i, module in pairs(modules) do
call(module.update, ...)
end
end
function uninit(...)
for i, module in pairs(modules) do
call(module.uninit, ...)
end
end

Binary file not shown.

Binary file not shown.

View File

@ -437,6 +437,7 @@ SET (STAR_EXT_LIBS ${STAR_EXT_LIBS}
${FREETYPE_LIBRARY} ${FREETYPE_LIBRARY}
${PNG_LIBRARY} ${PNG_LIBRARY}
${ZLIB_LIBRARY} ${ZLIB_LIBRARY}
opus
) )
IF (STAR_BUILD_GUI) IF (STAR_BUILD_GUI)

View File

@ -44,9 +44,16 @@ public:
virtual bool setCursorImage(const String& id, const ImageConstPtr& image, unsigned scale, const Vec2I& offset) = 0; virtual bool setCursorImage(const String& id, const ImageConstPtr& image, unsigned scale, const Vec2I& offset) = 0;
virtual void setAcceptingTextInput(bool acceptingTextInput) = 0; virtual void setAcceptingTextInput(bool acceptingTextInput) = 0;
virtual AudioFormat enableAudio() = 0; virtual AudioFormat enableAudio() = 0;
virtual void disableAudio() = 0; virtual void disableAudio() = 0;
typedef void (__cdecl* AudioCallback)(void* userdata, uint8_t* stream, int len);
virtual bool openAudioInputDevice(const char* name, int freq, int channels, void* userdata, AudioCallback callback) = 0;
virtual bool closeAudioInputDevice() = 0;
virtual void setClipboard(String text) = 0; virtual void setClipboard(String text) = 0;
virtual Maybe<String> getClipboard() = 0; virtual Maybe<String> getClipboard() = 0;

View File

@ -248,9 +248,15 @@ public:
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER)) if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER))
throw ApplicationException(strf("Couldn't initialize SDL Controller: {}", SDL_GetError())); throw ApplicationException(strf("Couldn't initialize SDL Controller: {}", SDL_GetError()));
Logger::info("Application: Initializing SDL Sound"); #ifdef STAR_SYSTEM_WINDOWS // Newer SDL is defaulting to xaudio2, which does not support audio capture
SDL_setenv("SDL_AUDIODRIVER", "directsound", 1);
#endif
Logger::info("Application: Initializing SDL Audio");
if (SDL_InitSubSystem(SDL_INIT_AUDIO)) if (SDL_InitSubSystem(SDL_INIT_AUDIO))
throw ApplicationException(strf("Couldn't initialize SDL Sound: {}", SDL_GetError())); throw ApplicationException(strf("Couldn't initialize SDL Audio: {}", SDL_GetError()));
Logger::info("Application: using Audio Driver '{}'", SDL_GetCurrentAudioDriver());
SDL_JoystickEventState(SDL_ENABLE); SDL_JoystickEventState(SDL_ENABLE);
@ -292,15 +298,15 @@ public:
}; };
SDL_AudioSpec obtained = {}; SDL_AudioSpec obtained = {};
m_sdlAudioDevice = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0); m_sdlAudioOutputDevice = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
if (!m_sdlAudioDevice) { if (!m_sdlAudioOutputDevice) {
Logger::error("Application: Could not open audio device, no sound available!"); Logger::error("Application: Could not open audio device, no sound available!");
} else if (obtained.freq != desired.freq || obtained.channels != desired.channels || obtained.format != desired.format) { } else if (obtained.freq != desired.freq || obtained.channels != desired.channels || obtained.format != desired.format) {
SDL_CloseAudioDevice(m_sdlAudioDevice); SDL_CloseAudioDevice(m_sdlAudioOutputDevice);
Logger::error("Application: Could not open 44.1khz / 16 bit stereo audio device, no sound available!"); Logger::error("Application: Could not open 44.1khz / 16 bit stereo audio device, no sound available!");
} else { } else {
Logger::info("Application: Opened default audio device with 44.1khz / 16 bit stereo audio, {} sample size buffer", obtained.samples); Logger::info("Application: Opened default audio device with 44.1khz / 16 bit stereo audio, {} sample size buffer", obtained.samples);
SDL_PauseAudioDevice(m_sdlAudioDevice, 0); SDL_PauseAudioDevice(m_sdlAudioOutputDevice, 0);
} }
m_renderer = make_shared<OpenGl20Renderer>(); m_renderer = make_shared<OpenGl20Renderer>();
@ -311,7 +317,10 @@ public:
~SdlPlatform() { ~SdlPlatform() {
SDL_CloseAudioDevice(m_sdlAudioDevice); if (m_sdlAudioOutputDevice)
SDL_CloseAudioDevice(m_sdlAudioOutputDevice);
closeAudioInputDevice();
m_renderer.reset(); m_renderer.reset();
@ -321,6 +330,43 @@ public:
SDL_Quit(); SDL_Quit();
} }
bool openAudioInputDevice(const char* name, int freq, int channels, void* userdata, SDL_AudioCallback callback) {
SDL_AudioSpec desired = {};
desired.freq = freq;
desired.format = AUDIO_S16SYS;
desired.samples = 1024;
desired.channels = channels;
desired.userdata = userdata;
desired.callback = callback;
closeAudioInputDevice();
SDL_AudioSpec obtained = {};
m_sdlAudioInputDevice = SDL_OpenAudioDevice(name, 1, &desired, &obtained, 0);
if (m_sdlAudioInputDevice) {
if (name)
Logger::info("Opened audio input device '{}'", name);
else
Logger::info("Opened default audio input device");
SDL_PauseAudioDevice(m_sdlAudioInputDevice, 0);
}
else
Logger::info("Failed to open audio input device: {}", SDL_GetError());
return m_sdlAudioInputDevice != 0;
}
bool closeAudioInputDevice() {
if (m_sdlAudioInputDevice) {
Logger::info("Closing audio input device");
SDL_CloseAudioDevice(m_sdlAudioInputDevice);
m_sdlAudioInputDevice = 0;
return true;
}
return false;
}
void cleanup() { void cleanup() {
m_cursorCache.ptr(m_currentCursor); m_cursorCache.ptr(m_currentCursor);
m_cursorCache.cleanup(); m_cursorCache.cleanup();
@ -397,7 +443,7 @@ public:
Logger::error("Application: threw exception during shutdown: {}", outputException(e, true)); Logger::error("Application: threw exception during shutdown: {}", outputException(e, true));
} }
SDL_CloseAudioDevice(m_sdlAudioDevice); SDL_CloseAudioDevice(m_sdlAudioOutputDevice);
m_SdlControllers.clear(); m_SdlControllers.clear();
SDL_SetCursor(NULL); SDL_SetCursor(NULL);
@ -569,6 +615,14 @@ private:
SDL_PauseAudio(true); SDL_PauseAudio(true);
} }
bool openAudioInputDevice(const char* name, int freq, int channels, void* userdata, AudioCallback callback) override {
return parent->openAudioInputDevice(name, freq, channels, userdata, callback);
};
bool closeAudioInputDevice() override {
return parent->closeAudioInputDevice();
};
float updateRate() const override { float updateRate() const override {
return parent->m_updateRate; return parent->m_updateRate;
} }
@ -803,7 +857,8 @@ private:
SDL_Window* m_sdlWindow = nullptr; SDL_Window* m_sdlWindow = nullptr;
SDL_GLContext m_sdlGlContext = nullptr; SDL_GLContext m_sdlGlContext = nullptr;
SDL_AudioDeviceID m_sdlAudioDevice = 0; SDL_AudioDeviceID m_sdlAudioOutputDevice = 0;
SDL_AudioDeviceID m_sdlAudioInputDevice = 0;
typedef std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> SDLGameControllerUPtr; typedef std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> SDLGameControllerUPtr;
StableHashMap<int, SDLGameControllerUPtr> m_SdlControllers; StableHashMap<int, SDLGameControllerUPtr> m_SdlControllers;

View File

@ -221,7 +221,7 @@ void Mixer::stopAll(float rampTime) {
p.first->stop(vel); p.first->stop(vel);
} }
void Mixer::read(int16_t* outBuffer, size_t frameCount) { void Mixer::read(int16_t* outBuffer, size_t frameCount, ExtraMixFunction extraMixFunction) {
// Make this method as least locky as possible by copying all the needed // Make this method as least locky as possible by copying all the needed
// member data before the expensive audio / effect stuff. // member data before the expensive audio / effect stuff.
unsigned sampleRate; unsigned sampleRate;
@ -326,7 +326,7 @@ void Mixer::read(int16_t* outBuffer, size_t frameCount) {
m_mixBuffer[s * channels + c] = 0; m_mixBuffer[s * channels + c] = 0;
} else { } else {
for (size_t c = 0; c < channels; ++c) for (size_t c = 0; c < channels; ++c)
m_mixBuffer[s * channels + c] = m_mixBuffer[s * channels + c] * volume; m_mixBuffer[s * channels + c] *= volume;
} }
} }
} }
@ -338,7 +338,8 @@ void Mixer::read(int16_t* outBuffer, size_t frameCount) {
float vol = lerp((float)s / frameCount, beginVolume * groupVolume * audioStopVolBegin, endVolume * groupEndVolume * audioStopVolEnd); float vol = lerp((float)s / frameCount, beginVolume * groupVolume * audioStopVolBegin, endVolume * groupEndVolume * audioStopVolEnd);
for (size_t c = 0; c < channels; ++c) { for (size_t c = 0; c < channels; ++c) {
float sample = m_mixBuffer[s * channels + c] * vol * audioState.positionalChannelVolumes[c] * audioInstance->m_volume.value; float sample = m_mixBuffer[s * channels + c] * vol * audioState.positionalChannelVolumes[c] * audioInstance->m_volume.value;
outBuffer[s * channels + c] = clamp(sample + outBuffer[s * channels + c], -32767.0f, 32767.0f); int16_t& outSample = outBuffer[s * channels + c];
outSample = clamp(sample + outSample, -32767.0f, 32767.0f);
} }
} }
@ -347,6 +348,9 @@ void Mixer::read(int16_t* outBuffer, size_t frameCount) {
} }
} }
if (extraMixFunction)
extraMixFunction(outBuffer, frameCount, channels);
{ {
MutexLocker locker(m_effectsMutex); MutexLocker locker(m_effectsMutex);
// Apply all active effects // Apply all active effects

View File

@ -95,6 +95,7 @@ private:
// Thread safe mixer class with basic effects support. // Thread safe mixer class with basic effects support.
class Mixer { class Mixer {
public: public:
typedef function<void(int16_t* buffer, size_t frames, unsigned channels)> ExtraMixFunction;
typedef function<void(int16_t* buffer, size_t frames, unsigned channels)> EffectFunction; typedef function<void(int16_t* buffer, size_t frames, unsigned channels)> EffectFunction;
typedef function<float(unsigned, Vec2F, float)> PositionalAttenuationFunction; typedef function<float(unsigned, Vec2F, float)> PositionalAttenuationFunction;
@ -126,7 +127,7 @@ public:
// Reads pending audio data. This is thread safe with the other Mixer // Reads pending audio data. This is thread safe with the other Mixer
// methods, but only one call to read may be active at a time. // methods, but only one call to read may be active at a time.
void read(int16_t* samples, size_t frameCount); void read(int16_t* samples, size_t frameCount, ExtraMixFunction extraMixFunction = {});
// Call within the main loop of the program using Mixer, calculates // Call within the main loop of the program using Mixer, calculates
// positional attenuation of audio and does cleanup. // positional attenuation of audio and does cleanup.

View File

@ -14,9 +14,13 @@
#include "StarWorldTemplate.hpp" #include "StarWorldTemplate.hpp"
#include "StarWorldClient.hpp" #include "StarWorldClient.hpp"
#include "StarRootLoader.hpp" #include "StarRootLoader.hpp"
#include "StarInput.hpp"
#include "StarVoice.hpp"
#include "StarCurve25519.hpp"
#include "StarInterfaceLuaBindings.hpp" #include "StarInterfaceLuaBindings.hpp"
#include "StarInputLuaBindings.hpp" #include "StarInputLuaBindings.hpp"
#include "StarVoiceLuaBindings.hpp"
namespace Star { namespace Star {
@ -171,6 +175,7 @@ void ClientApplication::applicationInit(ApplicationControllerPtr appController)
m_guiContext = make_shared<GuiContext>(m_mainMixer->mixer(), appController); m_guiContext = make_shared<GuiContext>(m_mainMixer->mixer(), appController);
m_input = make_shared<Input>(); m_input = make_shared<Input>();
m_voice = make_shared<Voice>(appController);
auto configuration = m_root->configuration(); auto configuration = m_root->configuration();
bool vsync = configuration->get("vsync").toBool(); bool vsync = configuration->get("vsync").toBool();
@ -204,6 +209,12 @@ void ClientApplication::applicationInit(ApplicationControllerPtr appController)
appController->setMaxFrameSkip(assets->json("/client.config:maxFrameSkip").toUInt()); appController->setMaxFrameSkip(assets->json("/client.config:maxFrameSkip").toUInt());
appController->setUpdateTrackWindow(assets->json("/client.config:updateTrackWindow").toFloat()); appController->setUpdateTrackWindow(assets->json("/client.config:updateTrackWindow").toFloat());
if (auto jVoice = configuration->get("voice"))
m_voice->loadJson(jVoice.toObject(), true);
m_voice->init();
m_voice->setLocalSpeaker(0);
} }
void ClientApplication::renderInit(RendererPtr renderer) { void ClientApplication::renderInit(RendererPtr renderer) {
@ -366,6 +377,12 @@ void ClientApplication::update() {
else if (m_state > MainAppState::Title) else if (m_state > MainAppState::Title)
updateRunning(); updateRunning();
// Swallow leftover encoded voice data if we aren't in-game to allow mic read to continue for settings.
if (m_state <= MainAppState::Title) {
DataStreamBuffer ext;
m_voice->send(ext);
} // TODO: directly disable encoding at menu so we don't have to do this
m_guiContext->cleanup(); m_guiContext->cleanup();
m_edgeKeyEvents.clear(); m_edgeKeyEvents.clear();
m_input->reset(); m_input->reset();
@ -417,8 +434,12 @@ void ClientApplication::render() {
} }
void ClientApplication::getAudioData(int16_t* sampleData, size_t frameCount) { void ClientApplication::getAudioData(int16_t* sampleData, size_t frameCount) {
if (m_mainMixer) if (m_mainMixer) {
m_mainMixer->read(sampleData, frameCount); m_mainMixer->read(sampleData, frameCount, [&](int16_t* buffer, size_t frames, unsigned channels) {
if (m_voice)
m_voice->mix(buffer, frames, channels);
});
}
} }
void ClientApplication::changeState(MainAppState newState) { void ClientApplication::changeState(MainAppState newState) {
@ -450,6 +471,8 @@ void ClientApplication::changeState(MainAppState newState) {
} }
m_cinematicOverlay->stop(); m_cinematicOverlay->stop();
m_voice->clearSpeakers();
if (auto p2pNetworkingService = appController()->p2pNetworkingService()) { if (auto p2pNetworkingService = appController()->p2pNetworkingService()) {
p2pNetworkingService->setJoinUnavailable(); p2pNetworkingService->setJoinUnavailable();
p2pNetworkingService->setAcceptingP2PConnections(false); p2pNetworkingService->setAcceptingP2PConnections(false);
@ -483,6 +506,7 @@ void ClientApplication::changeState(MainAppState newState) {
m_statistics = make_shared<Statistics>(m_root->toStoragePath("player"), appController()->statisticsService()); m_statistics = make_shared<Statistics>(m_root->toStoragePath("player"), appController()->statisticsService());
m_universeClient = make_shared<UniverseClient>(m_playerStorage, m_statistics); m_universeClient = make_shared<UniverseClient>(m_playerStorage, m_statistics);
m_universeClient->setLuaCallbacks("input", LuaBindings::makeInputCallbacks()); m_universeClient->setLuaCallbacks("input", LuaBindings::makeInputCallbacks());
m_universeClient->setLuaCallbacks("voice", LuaBindings::makeVoiceCallbacks());
m_mainMixer->setUniverseClient(m_universeClient); m_mainMixer->setUniverseClient(m_universeClient);
m_titleScreen = make_shared<TitleScreen>(m_playerStorage, m_mainMixer->mixer()); m_titleScreen = make_shared<TitleScreen>(m_playerStorage, m_mainMixer->mixer());
@ -601,6 +625,7 @@ void ClientApplication::changeState(MainAppState newState) {
m_worldPainter = make_shared<WorldPainter>(); m_worldPainter = make_shared<WorldPainter>();
m_mainInterface = make_shared<MainInterface>(m_universeClient, m_worldPainter, m_cinematicOverlay); m_mainInterface = make_shared<MainInterface>(m_universeClient, m_worldPainter, m_cinematicOverlay);
m_universeClient->setLuaCallbacks("interface", LuaBindings::makeInterfaceCallbacks(m_mainInterface.get())); m_universeClient->setLuaCallbacks("interface", LuaBindings::makeInterfaceCallbacks(m_mainInterface.get()));
m_universeClient->startLua();
m_mainMixer->setWorldPainter(m_worldPainter); m_mainMixer->setWorldPainter(m_worldPainter);
@ -839,13 +864,51 @@ void ClientApplication::updateRunning() {
if (checkDisconnection()) if (checkDisconnection())
return; return;
m_voice->setInput(m_input->bindHeld("opensb", "pushToTalk"));
DataStreamBuffer voiceData;
voiceData.setByteOrder(ByteOrder::LittleEndian);
//voiceData.writeBytes(VoiceBroadcastPrefix.utf8Bytes()); transmitting with SE compat for now
bool needstoSendVoice = m_voice->send(voiceData, 5000);
m_universeClient->update(); m_universeClient->update();
if (checkDisconnection()) if (checkDisconnection())
return; return;
if (auto worldClient = m_universeClient->worldClient()) if (auto worldClient = m_universeClient->worldClient()) {
auto& broadcastCallback = worldClient->broadcastCallback();
if (!broadcastCallback) {
broadcastCallback = [&](PlayerPtr player, StringView broadcast) -> bool {
auto& view = broadcast.utf8();
if (view.rfind(VoiceBroadcastPrefix.utf8(), 0) != NPos) {
auto entityId = player->entityId();
auto speaker = m_voice->speaker(connectionForEntity(entityId));
speaker->entityId = entityId;
speaker->name = player->name();
speaker->position = player->mouthPosition();
m_voice->receive(speaker, view.substr(VoiceBroadcastPrefix.utf8Size()));
}
return true;
};
}
if (worldClient->inWorld()) {
if (needstoSendVoice) {
auto signature = Curve25519::sign(voiceData.ptr(), voiceData.size());
std::string_view signatureView((char*)signature.data(), signature.size());
std::string_view audioDataView(voiceData.ptr(), voiceData.size());
auto broadcast = strf("data\0voice\0{}{}"s, signatureView, audioDataView);
worldClient->sendSecretBroadcast(broadcast, true);
}
if (auto mainPlayer = m_universeClient->mainPlayer()) {
auto localSpeaker = m_voice->localSpeaker();
localSpeaker->position = mainPlayer->position();
localSpeaker->entityId = mainPlayer->entityId();
localSpeaker->name = mainPlayer->name();
}
m_voice->setLocalSpeaker(worldClient->connection());
}
worldClient->setInteractiveHighlightMode(isActionTaken(InterfaceAction::ShowLabels)); worldClient->setInteractiveHighlightMode(isActionTaken(InterfaceAction::ShowLabels));
}
updateCamera(); updateCamera();

View File

@ -11,11 +11,13 @@
#include "StarErrorScreen.hpp" #include "StarErrorScreen.hpp"
#include "StarCinematic.hpp" #include "StarCinematic.hpp"
#include "StarKeyBindings.hpp" #include "StarKeyBindings.hpp"
#include "StarInput.hpp"
#include "StarMainApplication.hpp" #include "StarMainApplication.hpp"
namespace Star { namespace Star {
STAR_CLASS(Input);
STAR_CLASS(Voice);
class ClientApplication : public Application { class ClientApplication : public Application {
protected: protected:
virtual void startup(StringList const& cmdLineArgs) override; virtual void startup(StringList const& cmdLineArgs) override;
@ -76,6 +78,7 @@ private:
MainMixerPtr m_mainMixer; MainMixerPtr m_mainMixer;
GuiContextPtr m_guiContext; GuiContextPtr m_guiContext;
InputPtr m_input; InputPtr m_input;
VoicePtr m_voice;
// Valid after renderInit is called the first time // Valid after renderInit is called the first time
CinematicPtr m_cinematicOverlay; CinematicPtr m_cinematicOverlay;

View File

@ -21,6 +21,7 @@ SET (star_core_HEADERS
StarColor.hpp StarColor.hpp
StarCompression.hpp StarCompression.hpp
StarConfig.hpp StarConfig.hpp
StarCurve25519.hpp
StarDataStream.hpp StarDataStream.hpp
StarDataStreamDevices.hpp StarDataStreamDevices.hpp
StarDataStreamExtra.hpp StarDataStreamExtra.hpp
@ -133,6 +134,7 @@ SET (star_core_SOURCES
StarByteArray.cpp StarByteArray.cpp
StarColor.cpp StarColor.cpp
StarCompression.cpp StarCompression.cpp
StarCurve25519.cpp
StarDataStream.cpp StarDataStream.cpp
StarDataStreamDevices.cpp StarDataStreamDevices.cpp
StarDirectives.cpp StarDirectives.cpp

View File

@ -44,6 +44,7 @@ using std::mem_fn;
using std::ref; using std::ref;
using std::cref; using std::cref;
using namespace std::placeholders; using namespace std::placeholders;
using namespace std::string_literals;
using std::prev; using std::prev;
// using std::next; // using std::next;

View File

@ -0,0 +1,48 @@
#include "StarCurve25519.hpp"
#include "StarRandom.hpp"
#include "StarLogging.hpp"
#include "curve25519/include/curve25519_dh.h"
#include "curve25519/include/ed25519_signature.h"
namespace Star::Curve25519 {
struct KeySet {
PrivateKey privateKey;
PublicKey publicKey;
KeySet() {
SecretKey secret;
Random::randBytes(SecretKeySize).copyTo((char*)secret.data());
secret[0] &= 248;
secret[31] &= 127;
secret[31] |= 64;
ed25519_CreateKeyPair(publicKey.data(), privateKey.data(), nullptr, secret.data());
}
};
static KeySet const& staticKeys() {
static KeySet keys;
return keys;
}
PrivateKey const& privateKey() { return staticKeys().privateKey; }
Signature sign(void* data, size_t len) {
Signature signature;
ed25519_SignMessage(signature.data(), privateKey().data(), nullptr, (unsigned char*)data, len);
return signature;
}
bool verify(uint8_t const* signature, uint8_t const* publicKey, void* data, size_t len) {
return ed25519_VerifySignature(signature, publicKey, (unsigned char*)data, len);
}
PublicKey const& publicKey() { return staticKeys().publicKey; }
}

View File

@ -0,0 +1,25 @@
#ifndef STAR_CURVE_25519_HPP
#define STAR_CURVE_25519_HPP
#include "StarEncode.hpp"
#include "StarByteArray.hpp"
#include "StarArray.hpp"
namespace Star::Curve25519 {
constexpr size_t PublicKeySize = 32;
constexpr size_t SecretKeySize = 32;
constexpr size_t PrivateKeySize = 64;
constexpr size_t SignatureSize = 64;
typedef Array<uint8_t, PublicKeySize> PublicKey;
typedef Array<uint8_t, SecretKeySize> SecretKey;
typedef Array<uint8_t, PrivateKeySize> PrivateKey;
typedef Array<uint8_t, SignatureSize> Signature;
PublicKey const& publicKey();
Signature sign(void* data, size_t len);
bool verify(uint8_t const* signature, uint8_t const* publicKey, void* data, size_t len);
}
#endif

View File

@ -205,7 +205,7 @@ size_t DataStream::readVlqU(uint64_t& i) {
size_t bytesRead = Star::readVlqU(i, makeFunctionInputIterator([this]() { return this->read<uint8_t>(); })); size_t bytesRead = Star::readVlqU(i, makeFunctionInputIterator([this]() { return this->read<uint8_t>(); }));
if (bytesRead == NPos) if (bytesRead == NPos)
throw DataStreamException("Error reading VLQ encoded intenger!"); throw DataStreamException("Error reading VLQ encoded integer!");
return bytesRead; return bytesRead;
} }
@ -214,7 +214,7 @@ size_t DataStream::readVlqI(int64_t& i) {
size_t bytesRead = Star::readVlqI(i, makeFunctionInputIterator([this]() { return this->read<uint8_t>(); })); size_t bytesRead = Star::readVlqI(i, makeFunctionInputIterator([this]() { return this->read<uint8_t>(); }));
if (bytesRead == NPos) if (bytesRead == NPos)
throw DataStreamException("Error reading VLQ encoded intenger!"); throw DataStreamException("Error reading VLQ encoded integer!");
return bytesRead; return bytesRead;
} }

View File

@ -164,4 +164,11 @@ void DataStreamExternalBuffer::reset(char const* externalData, size_t len) {
m_buffer.reset(externalData, len); m_buffer.reset(externalData, len);
} }
void DataStreamExternalBuffer::readData(char* data, size_t len) {
m_buffer.readFull(data, len);
}
void DataStreamExternalBuffer::writeData(char const* data, size_t len) {
m_buffer.writeFull(data, len);
}
} }

View File

@ -140,6 +140,9 @@ public:
void reset(char const* externalData, size_t len); void reset(char const* externalData, size_t len);
void readData(char* data, size_t len) override;
void writeData(char const* data, size_t len) override;
private: private:
ExternalBuffer m_buffer; ExternalBuffer m_buffer;
}; };

View File

@ -1095,6 +1095,17 @@ LuaFunction LuaEngine::createRawFunction(lua_CFunction function) {
return LuaFunction(LuaDetail::LuaHandle(RefPtr<LuaEngine>(this), popHandle(m_state))); return LuaFunction(LuaDetail::LuaHandle(RefPtr<LuaEngine>(this), popHandle(m_state)));
} }
LuaFunction LuaEngine::createFunctionFromSource(int handleIndex, char const* contents, size_t size, char const* name) {
lua_checkstack(m_state, 2);
handleError(m_state, luaL_loadbuffer(m_state, contents, size, name));
pushHandle(m_state, handleIndex);
lua_setupvalue(m_state, -2, 1);
return LuaFunction(LuaDetail::LuaHandle(RefPtr<LuaEngine>(this), popHandle(m_state)));
}
void LuaEngine::pushLuaValue(lua_State* state, LuaValue const& luaValue) { void LuaEngine::pushLuaValue(lua_State* state, LuaValue const& luaValue) {
lua_checkstack(state, 1); lua_checkstack(state, 1);

View File

@ -310,6 +310,7 @@ public:
using LuaTable::contains; using LuaTable::contains;
using LuaTable::remove; using LuaTable::remove;
using LuaTable::engine; using LuaTable::engine;
using LuaTable::handleIndex;
// Splits the path by '.' character, so can get / set values in tables inside // Splits the path by '.' character, so can get / set values in tables inside
// other tables. If any table in the path is not a table but is accessed as // other tables. If any table in the path is not a table but is accessed as
@ -530,6 +531,8 @@ public:
LuaFunction createRawFunction(lua_CFunction func); LuaFunction createRawFunction(lua_CFunction func);
LuaFunction createFunctionFromSource(int handleIndex, char const* contents, size_t size, char const* name);
LuaThread createThread(); LuaThread createThread();
template <typename T> template <typename T>

View File

@ -1,10 +1,29 @@
SET (OPUS_INSTALL_PKG_CONFIG_MODULE OFF)
SET (OPUS_INSTALL_CMAKE_CONFIG_MODULE OFF)
SET (OPUS_X86_MAY_HAVE_AVX OFF)
SET (OPUS_X86_MAY_HAVE_SSE4_1 OFF)
SET (OPUS_ENABLE_FLOAT_API ON)
SET (OPUS_STACK_PROTECTOR OFF)
SET (OPUS_NONTHREADSAFE_PSEUDOSTACK OFF)
SET (OPUS_USE_ALLOCA ON)
ADD_SUBDIRECTORY (opus)
IF (OPUS_NONTHREADSAFE_PSEUDOSTACK)
MESSAGE (FATAL_ERROR "Opus should not be using NONTHREADSAFE_PSEUDOSTACK")
ENDIF ()
INCLUDE_DIRECTORIES ( INCLUDE_DIRECTORIES (
${STAR_EXTERN_INCLUDES} ${STAR_EXTERN_INCLUDES}
opus/include
fmt fmt
lua lua
) )
SET (star_extern_HEADERS SET (star_extern_HEADERS
curve25519/include/curve25519_dh.h
curve25519/include/ed25519_signature.h
curve25519/include/external_calls.h
fmt/core.h fmt/core.h
fmt/format.h fmt/format.h
fmt/format-inl.h fmt/format-inl.h
@ -22,6 +41,14 @@ SET (star_extern_HEADERS
) )
SET (star_extern_SOURCES SET (star_extern_SOURCES
curve25519/source/sha512.c
curve25519/source/curve25519_dh.c
curve25519/source/curve25519_mehdi.c
curve25519/source/curve25519_order.c
curve25519/source/curve25519_utils.c
curve25519/source/custom_blind.c
curve25519/source/ed25519_sign.c
curve25519/source/ed25519_verify.c
xxhash.c xxhash.c
fmt/format.cc fmt/format.cc
lua/lapi.c lua/lapi.c
@ -60,3 +87,4 @@ SET (star_extern_SOURCES
) )
ADD_LIBRARY (star_extern OBJECT ${star_extern_SOURCES} ${star_extern_HEADERS}) ADD_LIBRARY (star_extern OBJECT ${star_extern_SOURCES} ${star_extern_HEADERS})
TARGET_LINK_LIBRARIES(star_extern PUBLIC opus)

View File

@ -0,0 +1,53 @@
/* The MIT License (MIT)
*
* Copyright (c) 2015 mehdi sotoodeh
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __curve25519_dh_key_exchange_h__
#define __curve25519_dh_key_exchange_h__
#ifdef __cplusplus
extern "C" {
#endif
/* Return public key associated with sk */
/* sk will be trimmed on return */
void curve25519_dh_CalculatePublicKey(
unsigned char *pk, /* [32-bytes] OUT: Public key */
unsigned char *sk); /* [32-bytes] IN/OUT: Your secret key */
/* Faster alternative for curve25519_dh_CalculatePublicKey */
/* sk will be trimmed on return */
void curve25519_dh_CalculatePublicKey_fast(
unsigned char *pk, /* [32-bytes] OUT: Public key */
unsigned char *sk); /* [32-bytes] IN/OUT: Your secret key */
/* sk will be trimmed on return */
void curve25519_dh_CreateSharedKey(
unsigned char *shared, /* [32-bytes] OUT: Created shared key */
const unsigned char *pk, /* [32-bytes] IN: Other side's public key */
unsigned char *sk); /* [32-bytes] IN/OUT: Your secret key */
#ifdef __cplusplus
}
#endif
#endif /* __curve25519_dh_key_exchange_h__ */

View File

@ -0,0 +1,98 @@
/* The MIT License (MIT)
*
* Copyright (c) 2015 mehdi sotoodeh
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __ed25519_signature_h__
#define __ed25519_signature_h__
#ifdef __cplusplus
extern "C" {
#endif
/* -- ed25519-sign ------------------------------------------------------------- */
#define ed25519_public_key_size 32
#define ed25519_secret_key_size 32
#define ed25519_private_key_size 64
#define ed25519_signature_size 64
/* Generate public key associated with the secret key */
void ed25519_CreateKeyPair(
unsigned char *pubKey, /* OUT: public key */
unsigned char *privKey, /* OUT: private key */
const void *blinding, /* IN: [optional] null or blinding context */
const unsigned char *sk); /* IN: secret key (32 bytes) */
/* Generate message signature */
void ed25519_SignMessage(
unsigned char *signature, /* OUT:[64 bytes] signature (R,S) */
const unsigned char *privKey, /* IN: [64 bytes] private key (sk,pk) */
const void *blinding, /* IN: [optional] null or blinding context */
const unsigned char *msg, /* IN: [msg_size bytes] message to sign */
size_t msg_size); /* IN: size of message */
void *ed25519_Blinding_Init(
void *context, /* IO: null or ptr blinding context */
const unsigned char *seed, /* IN: [size bytes] random blinding seed */
size_t size); /* IN: size of blinding seed */
void ed25519_Blinding_Finish(
void *context); /* IN: blinding context */
/* -- ed25519-verify ----------------------------------------------------------- */
/* Single-phased signature validation.
Returns 1 for SUCCESS and 0 for FAILURE
*/
int ed25519_VerifySignature(
const unsigned char *signature, /* IN: [64 bytes] signature (R,S) */
const unsigned char *publicKey, /* IN: [32 bytes] public key */
const unsigned char *msg, /* IN: [msg_size bytes] message to sign */
size_t msg_size); /* IN: size of message */
/* First part of two-phase signature validation.
This function creates context specifc to a given public key.
Needs to be called once per public key
*/
void * ed25519_Verify_Init(
void *context, /* IO: null or verify context to use */
const unsigned char *publicKey); /* IN: [32 bytes] public key */
/* Second part of two-phase signature validation.
Input context is output of ed25519_Verify_Init() for associated public key.
Call it once for each message/signature pairs
Returns 1 for SUCCESS and 0 for FAILURE
*/
int ed25519_Verify_Check(
const void *context, /* IN: created by ed25519_Verify_Init */
const unsigned char *signature, /* IN: signature (R,S) */
const unsigned char *msg, /* IN: message to sign */
size_t msg_size); /* IN: size of message */
/* Free up context memory */
void ed25519_Verify_Finish(void *ctx);
#ifdef __cplusplus
}
#endif
#endif /* __ed25519_signature_h__ */

View File

@ -0,0 +1,36 @@
/* The MIT License (MIT)
*
* Copyright (c) 2015 mehdi sotoodeh
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __external_calls_h__
#define __external_calls_h__
#include <memory.h>
#include <stdlib.h>
#define mem_alloc(size) malloc(size)
#define mem_free(addr) free(addr)
#define mem_clear(addr,size) memset(addr,0,size)
#define mem_fill(addr,data,size) memset(addr,data,size)
#endif /* __external_calls_h__ */

View File

@ -0,0 +1,121 @@
/* The MIT License (MIT)
*
* Copyright (c) 2015 mehdi sotoodeh
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __curve25519_base_type_h__
#define __curve25519_base_type_h__
#include <stdint.h>
/* Little-endian as default */
#ifndef ECP_CONFIG_BIG_ENDIAN
#define ECP_CONFIG_LITTLE_ENDIAN
#endif
typedef unsigned char U8;
typedef signed char S8;
#if defined(_MSC_VER)
typedef unsigned __int16 U16;
typedef signed __int16 S16;
typedef unsigned __int32 U32;
typedef signed __int32 S32;
typedef unsigned __int64 U64;
typedef signed __int64 S64;
#else
typedef uint16_t U16;
typedef int16_t S16;
typedef uint32_t U32;
typedef int32_t S32;
typedef uint64_t U64;
typedef int64_t S64;
#endif
typedef unsigned int SZ;
#ifdef ECP_CONFIG_BIG_ENDIAN
typedef union
{
U16 u16;
S16 s16;
U8 bytes[2];
struct { U8 b1, b0; } u8;
struct { S8 b1; U8 b0; } s8;
} M16;
typedef union
{
U32 u32;
S32 s32;
U8 bytes[4];
struct { U16 w1, w0; } u16;
struct { S16 w1; U16 w0; } s16;
struct { U8 b3, b2, b1, b0; } u8;
struct { M16 hi, lo; } m16;
} M32;
typedef union
{
U64 u64;
S64 s64;
U8 bytes[8];
struct { U32 hi, lo; } u32;
struct { S32 hi; U32 lo; } s32;
struct { U16 w3, w2, w1, w0; } u16;
struct { U8 b7, b6, b5, b4, b3, b2, b1, b0; } u8;
struct { M32 hi, lo; } m32;
} M64;
#else
typedef union
{
U16 u16;
S16 s16;
U8 bytes[2];
struct { U8 b0, b1; } u8;
struct { U8 b0; S8 b1; } s8;
} M16;
typedef union
{
U32 u32;
S32 s32;
U8 bytes[4];
struct { U16 w0, w1; } u16;
struct { U16 w0; S16 w1; } s16;
struct { U8 b0, b1, b2, b3; } u8;
struct { M16 lo, hi; } m16;
} M32;
typedef union
{
U64 u64;
S64 s64;
U8 bytes[8];
struct { U32 lo, hi; } u32;
struct { U32 lo; S32 hi; } s32;
struct { U16 w0, w1, w2, w3; } u16;
struct { U8 b0, b1, b2, b3, b4, b5, b6, b7; } u8;
struct { M32 lo, hi; } m32;
} M64;
#endif
#define IN
#define OUT
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,208 @@
/* The MIT License (MIT)
*
* Copyright (c) 2015 mehdi sotoodeh
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "../include/external_calls.h"
#include "curve25519_mehdi.h"
typedef struct
{
U_WORD X[K_WORDS]; /* x = X/Z */
U_WORD Z[K_WORDS]; /* */
} XZ_POINT;
extern const U_WORD _w_P[K_WORDS];
extern EDP_BLINDING_CTX edp_custom_blinding;
/* x coordinate of base point */
const U8 ecp_BasePoint[32] = {
9,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 };
/* Y = X + X */
void ecp_MontDouble(XZ_POINT *Y, const XZ_POINT *X)
{
U_WORD A[K_WORDS], B[K_WORDS];
/* x2 = (x+z)^2 * (x-z)^2 */
/* z2 = ((x+z)^2 - (x-z)^2)*((x+z)^2 + ((A-2)/4)((x+z)^2 - (x-z)^2)) */
ecp_AddReduce(A, X->X, X->Z); /* A = (x+z) */
ecp_SubReduce(B, X->X, X->Z); /* B = (x-z) */
ecp_SqrReduce(A, A); /* A = (x+z)^2 */
ecp_SqrReduce(B, B); /* B = (x-z)^2 */
ecp_MulReduce(Y->X, A, B); /* x2 = (x+z)^2 * (x-z)^2 */
ecp_SubReduce(B, A, B); /* B = (x+z)^2 - (x-z)^2 */
/* (486662-2)/4 = 121665 */
ecp_WordMulAddReduce(A, A, 121665, B);
ecp_MulReduce(Y->Z, A, B); /* z2 = (B)*((x+z)^2 + ((A-2)/4)(B)) */
}
/* return P = P + Q, Q = 2Q */
void ecp_Mont(XZ_POINT *P, XZ_POINT *Q, IN const U_WORD *Base)
{
U_WORD A[K_WORDS], B[K_WORDS], C[K_WORDS], D[K_WORDS], E[K_WORDS];
/* x3 = ((x1-z1)(x2+z2) + (x1+z1)(x2-z2))^2*zb zb=1 */
/* z3 = ((x1-z1)(x2+z2) - (x1+z1)(x2-z2))^2*xb xb=Base */
ecp_SubReduce(A, P->X, P->Z); /* A = x1-z1 */
ecp_AddReduce(B, P->X, P->Z); /* B = x1+z1 */
ecp_SubReduce(C, Q->X, Q->Z); /* C = x2-z2 */
ecp_AddReduce(D, Q->X, Q->Z); /* D = x2+z2 */
ecp_MulReduce(A, A, D); /* A = (x1-z1)(x2+z2) */
ecp_MulReduce(B, B, C); /* B = (x1+z1)(x2-z2) */
ecp_AddReduce(E, A, B); /* E = (x1-z1)(x2+z2) + (x1+z1)(x2-z2) */
ecp_SubReduce(B, A, B); /* B = (x1-z1)(x2+z2) - (x1+z1)(x2-z2) */
ecp_SqrReduce(P->X, E); /* x3 = ((x1-z1)(x2+z2) + (x1+z1)(x2-z2))^2 */
ecp_SqrReduce(A, B); /* A = ((x1-z1)(x2+z2) - (x1+z1)(x2-z2))^2 */
ecp_MulReduce(P->Z, A, Base); /* z3 = ((x1-z1)(x2+z2) - (x1+z1)(x2-z2))^2*Base */
/* x4 = (x2+z2)^2 * (x2-z2)^2 */
/* z4 = ((x2+z2)^2 - (x2-z2)^2)*((x2+z2)^2 + 121665((x2+z2)^2 - (x2-z2)^2)) */
/* C = (x2-z2) */
/* D = (x2+z2) */
ecp_SqrReduce(A, D); /* A = (x2+z2)^2 */
ecp_SqrReduce(B, C); /* B = (x2-z2)^2 */
ecp_MulReduce(Q->X, A, B); /* x4 = (x2+z2)^2 * (x2-z2)^2 */
ecp_SubReduce(B, A, B); /* B = (x2+z2)^2 - (x2-z2)^2 */
ecp_WordMulAddReduce(A, A, 121665, B);
ecp_MulReduce(Q->Z, A, B); /* z4 = B*((x2+z2)^2 + 121665*B) */
}
/* Constant-time measure: */
/* Use different set of parameters for bit=0 or bit=1 with no conditional jump */
/* */
#define ECP_MONT(n) j = (k >> n) & 1; ecp_Mont(PP[j], QP[j], X)
/* -------------------------------------------------------------------------- */
/* Return point Q = k*P */
/* K in a little-endian byte array */
void ecp_PointMultiply(
OUT U8 *PublicKey,
IN const U8 *BasePoint,
IN const U8 *SecretKey,
IN int len)
{
int i, j, k;
U_WORD X[K_WORDS];
XZ_POINT P, Q, *PP[2], *QP[2];
ecp_BytesToWords(X, BasePoint);
/* 1: P = (2k+1)G, Q = (2k+2)G */
/* 0: Q = (2k+1)G, P = (2k)G */
/* Find first non-zero bit */
while (len-- > 0)
{
k = SecretKey[len];
for (i = 0; i < 8; i++, k <<= 1)
{
/* P = kG, Q = (k+1)G */
if (k & 0x80)
{
/* We have first non-zero bit
// This is always bit 254 for keys created according to the spec.
// Start with randomized base point
*/
ecp_Add(P.Z, X, edp_custom_blinding.zr); /* P.Z = random */
ecp_MulReduce(P.X, X, P.Z);
ecp_MontDouble(&Q, &P);
PP[1] = &P; PP[0] = &Q;
QP[1] = &Q; QP[0] = &P;
/* Everything we reference in the below loop are on the stack
// and already touched (cached)
*/
while (++i < 8) { k <<= 1; ECP_MONT(7); }
while (len > 0)
{
k = SecretKey[--len];
ECP_MONT(7);
ECP_MONT(6);
ECP_MONT(5);
ECP_MONT(4);
ECP_MONT(3);
ECP_MONT(2);
ECP_MONT(1);
ECP_MONT(0);
}
ecp_Inverse(Q.Z, P.Z);
ecp_MulMod(X, P.X, Q.Z);
ecp_WordsToBytes(PublicKey, X);
return;
}
}
}
/* K is 0 */
mem_fill(PublicKey, 0, 32);
}
/* -- DH key exchange interfaces ----------------------------------------- */
/* Return R = a*P where P is curve25519 base point */
void x25519_BasePointMultiply(OUT U8 *r, IN const U8 *sk)
{
Ext_POINT S;
U_WORD t[K_WORDS];
ecp_BytesToWords(t, sk);
edp_BasePointMult(&S, t, edp_custom_blinding.zr);
/* Convert ed25519 point to x25519 point */
/* u = (1 + y)/(1 - y) = (Z + Y)/(Z - Y) */
ecp_AddReduce(S.t, S.z, S.y);
ecp_SubReduce(S.z, S.z, S.y);
ecp_Inverse(S.z, S.z);
ecp_MulMod(S.t, S.t, S.z);
ecp_WordsToBytes(r, S.t);
}
/* Return public key associated with sk */
void curve25519_dh_CalculatePublicKey_fast(
unsigned char *pk, /* [32-bytes] OUT: Public key */
unsigned char *sk) /* [32-bytes] IN/OUT: Your secret key */
{
ecp_TrimSecretKey(sk);
/* Use faster method */
x25519_BasePointMultiply(pk, sk);
}
/* Return public key associated with sk */
void curve25519_dh_CalculatePublicKey(
unsigned char *pk, /* [32-bytes] OUT: Public key */
unsigned char *sk) /* [32-bytes] IN/OUT: Your secret key */
{
ecp_TrimSecretKey(sk);
ecp_PointMultiply(pk, ecp_BasePoint, sk, 32);
}
/* Create a shared secret */
void curve25519_dh_CreateSharedKey(
unsigned char *shared, /* [32-bytes] OUT: Created shared key */
const unsigned char *pk, /* [32-bytes] IN: Other side's public key */
unsigned char *sk) /* [32-bytes] IN/OUT: Your secret key */
{
ecp_TrimSecretKey(sk);
ecp_PointMultiply(shared, pk, sk, 32);
}

View File

@ -0,0 +1,410 @@
/* The MIT License (MIT)
*
* Copyright (c) 2015 mehdi sotoodeh
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "../include/external_calls.h"
#include "curve25519_mehdi.h"
/*
The curve used is y2 = x^3 + 486662x^2 + x, a Montgomery curve, over
the prime field defined by the prime number 2^255 - 19, and it uses the
base point x = 9.
Protocol uses compressed elliptic point (only X coordinates), so it
allows for efficient use of the Montgomery ladder for ECDH, using only
XZ coordinates.
The curve is birationally equivalent to Ed25519 (Twisted Edwards curve).
b = 256
p = 2**255 - 19
l = 2**252 + 27742317777372353535851937790883648493
This library is a constant-time implementation of field operations
*/
typedef struct
{
U32 X[8]; /* x = X/Z */
U32 Z[8]; /* */
} XZ_POINT;
const U32 _w_P[8] = {
0xFFFFFFED,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,
0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0x7FFFFFFF
};
/* Maximum number of prime p that fits into 256-bits */
const U32 _w_maxP[8] = { /* 2*P < 2**256 */
0xFFFFFFDA,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,
0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF
};
void ecp_SetValue(U32* X, U32 value)
{
X[0] = value;
X[1] = X[2] = X[3] = X[4] = X[5] = X[6] = X[7] = 0;
}
/* Y = X */
void ecp_Copy(U32* Y, const U32* X)
{
memcpy(Y, X, 8*sizeof(U32));
}
int ecp_CmpNE(const U32* X, const U32* Y)
{
return ((X[0] ^ Y[0]) | (X[1] ^ Y[1]) | (X[2] ^ Y[2]) | (X[3] ^ Y[3]) |
(X[4] ^ Y[4]) | (X[5] ^ Y[5]) | (X[6] ^ Y[6]) | (X[7] ^ Y[7]));
}
int ecp_CmpLT(const U32* X, const U32* Y)
{
U32 T[8];
return ecp_Sub(T, X, Y);
}
#define ECP_ADD_C0(Y,X,V) c.u64 = (U64)(X) + (V); Y = c.u32.lo;
#define ECP_ADD_C1(Y,X) c.u64 = (U64)(X) + c.u32.hi; Y = c.u32.lo;
#define ECP_SUB_C0(Y,X,V) c.s64 = (U64)(X) - (V); Y = c.u32.lo;
#define ECP_SUB_C1(Y,X) c.s64 = (U64)(X) + (S64)c.s32.hi; Y = c.u32.lo;
#define ECP_MULSET_W0(Y,b,X) c.u64 = (U64)(b)*(X); Y = c.u32.lo;
#define ECP_MULSET_W1(Y,b,X) c.u64 = (U64)(b)*(X) + c.u32.hi; Y = c.u32.lo;
#define ECP_MULADD_W0(Z,Y,b,X) c.u64 = (U64)(b)*(X) + (Y); Z = c.u32.lo;
#define ECP_MULADD_W1(Z,Y,b,X) c.u64 = (U64)(b)*(X) + (U64)(Y) + c.u32.hi; Z = c.u32.lo;
#define ECP_ADD32(Z,X,Y) c.u64 = (U64)(X) + (Y); Z = c.u32.lo;
#define ECP_ADC32(Z,X,Y) c.u64 = (U64)(X) + (U64)(Y) + c.u32.hi; Z = c.u32.lo;
#define ECP_SUB32(Z,X,Y) b.s64 = (S64)(X) - (Y); Z = b.s32.lo;
#define ECP_SBC32(Z,X,Y) b.s64 = (S64)(X) - (U64)(Y) + b.s32.hi; Z = b.s32.lo;
/* Computes Z = X+Y */
U32 ecp_Add(U32* Z, const U32* X, const U32* Y)
{
M64 c;
ECP_ADD32(Z[0], X[0], Y[0]);
ECP_ADC32(Z[1], X[1], Y[1]);
ECP_ADC32(Z[2], X[2], Y[2]);
ECP_ADC32(Z[3], X[3], Y[3]);
ECP_ADC32(Z[4], X[4], Y[4]);
ECP_ADC32(Z[5], X[5], Y[5]);
ECP_ADC32(Z[6], X[6], Y[6]);
ECP_ADC32(Z[7], X[7], Y[7]);
return c.u32.hi;
}
/* Computes Z = X-Y */
S32 ecp_Sub(U32* Z, const U32* X, const U32* Y)
{
M64 b;
ECP_SUB32(Z[0], X[0], Y[0]);
ECP_SBC32(Z[1], X[1], Y[1]);
ECP_SBC32(Z[2], X[2], Y[2]);
ECP_SBC32(Z[3], X[3], Y[3]);
ECP_SBC32(Z[4], X[4], Y[4]);
ECP_SBC32(Z[5], X[5], Y[5]);
ECP_SBC32(Z[6], X[6], Y[6]);
ECP_SBC32(Z[7], X[7], Y[7]);
return b.s32.hi;
}
/* Computes Z = X+Y mod P */
void ecp_AddReduce(U32* Z, const U32* X, const U32* Y)
{
M64 c;
c.u32.hi = ecp_Add(Z, X, Y) * 38;
/* Z += c.u32.hi * 38 */
ECP_ADD_C0(Z[0], Z[0], c.u32.hi);
ECP_ADD_C1(Z[1], Z[1]);
ECP_ADD_C1(Z[2], Z[2]);
ECP_ADD_C1(Z[3], Z[3]);
ECP_ADD_C1(Z[4], Z[4]);
ECP_ADD_C1(Z[5], Z[5]);
ECP_ADD_C1(Z[6], Z[6]);
ECP_ADD_C1(Z[7], Z[7]);
/* One more carry at most */
ECP_ADD_C0(Z[0], Z[0], c.u32.hi*38);
ECP_ADD_C1(Z[1], Z[1]);
ECP_ADD_C1(Z[2], Z[2]);
ECP_ADD_C1(Z[3], Z[3]);
ECP_ADD_C1(Z[4], Z[4]);
ECP_ADD_C1(Z[5], Z[5]);
ECP_ADD_C1(Z[6], Z[6]);
ECP_ADD_C1(Z[7], Z[7]);
}
/* Computes Z = X-Y mod P */
void ecp_SubReduce(U32* Z, const U32* X, const U32* Y)
{
M64 c;
c.u32.hi = ecp_Sub(Z, X, Y) & 38;
ECP_SUB_C0(Z[0], Z[0], c.u32.hi);
ECP_SUB_C1(Z[1], Z[1]);
ECP_SUB_C1(Z[2], Z[2]);
ECP_SUB_C1(Z[3], Z[3]);
ECP_SUB_C1(Z[4], Z[4]);
ECP_SUB_C1(Z[5], Z[5]);
ECP_SUB_C1(Z[6], Z[6]);
ECP_SUB_C1(Z[7], Z[7]);
ECP_SUB_C0(Z[0], Z[0], c.u32.hi & 38);
ECP_SUB_C1(Z[1], Z[1]);
ECP_SUB_C1(Z[2], Z[2]);
ECP_SUB_C1(Z[3], Z[3]);
ECP_SUB_C1(Z[4], Z[4]);
ECP_SUB_C1(Z[5], Z[5]);
ECP_SUB_C1(Z[6], Z[6]);
ECP_SUB_C1(Z[7], Z[7]);
}
void ecp_Mod(U32 *X)
{
U32 T[8];
U32 c = (U32)ecp_Sub(X, X, _w_P);
/* set T = 0 if c=0, else T = P */
T[0] = c & 0xFFFFFFED;
T[1] = T[2] = T[3] = T[4] = T[5] = T[6] = c;
T[7] = c >> 1;
ecp_Add(X, X, T); /* X += 0 or P */
/* In case there is another P there */
c = (U32)ecp_Sub(X, X, _w_P);
/* set T = 0 if c=0, else T = P */
T[0] = c & 0xFFFFFFED;
T[1] = T[2] = T[3] = T[4] = T[5] = T[6] = c;
T[7] = c >> 1;
ecp_Add(X, X, T); /* X += 0 or P */
}
/* Computes Y = b*X */
static void ecp_mul_set(U32* Y, U32 b, const U32* X)
{
M64 c;
ECP_MULSET_W0(Y[0], b, X[0]);
ECP_MULSET_W1(Y[1], b, X[1]);
ECP_MULSET_W1(Y[2], b, X[2]);
ECP_MULSET_W1(Y[3], b, X[3]);
ECP_MULSET_W1(Y[4], b, X[4]);
ECP_MULSET_W1(Y[5], b, X[5]);
ECP_MULSET_W1(Y[6], b, X[6]);
ECP_MULSET_W1(Y[7], b, X[7]);
Y[8] = c.u32.hi;
}
/* Computes Y += b*X */
/* Addition is performed on lower 8-words of Y */
static void ecp_mul_add(U32* Y, U32 b, const U32* X)
{
M64 c;
ECP_MULADD_W0(Y[0], Y[0], b, X[0]);
ECP_MULADD_W1(Y[1], Y[1], b, X[1]);
ECP_MULADD_W1(Y[2], Y[2], b, X[2]);
ECP_MULADD_W1(Y[3], Y[3], b, X[3]);
ECP_MULADD_W1(Y[4], Y[4], b, X[4]);
ECP_MULADD_W1(Y[5], Y[5], b, X[5]);
ECP_MULADD_W1(Y[6], Y[6], b, X[6]);
ECP_MULADD_W1(Y[7], Y[7], b, X[7]);
Y[8] = c.u32.hi;
}
/* Computes Z = Y + b*X and return carry */
void ecp_WordMulAddReduce(U32 *Z, const U32* Y, U32 b, const U32* X)
{
M64 c;
ECP_MULADD_W0(Z[0], Y[0], b, X[0]);
ECP_MULADD_W1(Z[1], Y[1], b, X[1]);
ECP_MULADD_W1(Z[2], Y[2], b, X[2]);
ECP_MULADD_W1(Z[3], Y[3], b, X[3]);
ECP_MULADD_W1(Z[4], Y[4], b, X[4]);
ECP_MULADD_W1(Z[5], Y[5], b, X[5]);
ECP_MULADD_W1(Z[6], Y[6], b, X[6]);
ECP_MULADD_W1(Z[7], Y[7], b, X[7]);
/* Z += c.u32.hi * 38 */
ECP_MULADD_W0(Z[0], Z[0], c.u32.hi, 38);
ECP_ADD_C1(Z[1], Z[1]);
ECP_ADD_C1(Z[2], Z[2]);
ECP_ADD_C1(Z[3], Z[3]);
ECP_ADD_C1(Z[4], Z[4]);
ECP_ADD_C1(Z[5], Z[5]);
ECP_ADD_C1(Z[6], Z[6]);
ECP_ADD_C1(Z[7], Z[7]);
/* One more time at most */
ECP_MULADD_W0(Z[0], Z[0], c.u32.hi, 38);
ECP_ADD_C1(Z[1], Z[1]);
ECP_ADD_C1(Z[2], Z[2]);
ECP_ADD_C1(Z[3], Z[3]);
ECP_ADD_C1(Z[4], Z[4]);
ECP_ADD_C1(Z[5], Z[5]);
ECP_ADD_C1(Z[6], Z[6]);
ECP_ADD_C1(Z[7], Z[7]);
}
/* Computes Z = X*Y mod P. */
/* Output fits into 8 words but could be greater than P */
void ecp_MulReduce(U32* Z, const U32* X, const U32* Y)
{
U32 T[16];
ecp_mul_set(T+0, X[0], Y);
ecp_mul_add(T+1, X[1], Y);
ecp_mul_add(T+2, X[2], Y);
ecp_mul_add(T+3, X[3], Y);
ecp_mul_add(T+4, X[4], Y);
ecp_mul_add(T+5, X[5], Y);
ecp_mul_add(T+6, X[6], Y);
ecp_mul_add(T+7, X[7], Y);
/* We have T = X*Y, now do the reduction in size */
ecp_WordMulAddReduce(Z, T, 38, T+8);
}
/* Computes Z = X*Y */
void ecp_Mul(U32* Z, const U32* X, const U32* Y)
{
ecp_mul_set(Z+0, X[0], Y);
ecp_mul_add(Z+1, X[1], Y);
ecp_mul_add(Z+2, X[2], Y);
ecp_mul_add(Z+3, X[3], Y);
ecp_mul_add(Z+4, X[4], Y);
ecp_mul_add(Z+5, X[5], Y);
ecp_mul_add(Z+6, X[6], Y);
ecp_mul_add(Z+7, X[7], Y);
}
/* Computes Z = X*Y mod P. */
void ecp_SqrReduce(U32* Y, const U32* X)
{
/* TBD: Implementation is based on multiply */
/* Optimize for squaring */
U32 T[16];
ecp_mul_set(T+0, X[0], X);
ecp_mul_add(T+1, X[1], X);
ecp_mul_add(T+2, X[2], X);
ecp_mul_add(T+3, X[3], X);
ecp_mul_add(T+4, X[4], X);
ecp_mul_add(T+5, X[5], X);
ecp_mul_add(T+6, X[6], X);
ecp_mul_add(T+7, X[7], X);
/* We have T = X*X, now do the reduction in size */
ecp_WordMulAddReduce(Y, T, 38, T+8);
}
/* Computes Z = X*Y mod P. */
void ecp_MulMod(U32* Z, const U32* X, const U32* Y)
{
ecp_MulReduce(Z, X, Y);
ecp_Mod(Z);
}
/* Courtesy of DJB */
/* Return out = 1/z mod P */
void ecp_Inverse(U32 *out, const U32 *z)
{
int i;
U32 t0[8],t1[8],z2[8],z9[8],z11[8];
U32 z2_5_0[8],z2_10_0[8],z2_20_0[8],z2_50_0[8],z2_100_0[8];
/* 2 */ ecp_SqrReduce(z2,z);
/* 4 */ ecp_SqrReduce(t1,z2);
/* 8 */ ecp_SqrReduce(t0,t1);
/* 9 */ ecp_MulReduce(z9,t0,z);
/* 11 */ ecp_MulReduce(z11,z9,z2);
/* 22 */ ecp_SqrReduce(t0,z11);
/* 2^5 - 2^0 = 31 */ ecp_MulReduce(z2_5_0,t0,z9);
/* 2^6 - 2^1 */ ecp_SqrReduce(t0,z2_5_0);
/* 2^7 - 2^2 */ ecp_SqrReduce(t1,t0);
/* 2^8 - 2^3 */ ecp_SqrReduce(t0,t1);
/* 2^9 - 2^4 */ ecp_SqrReduce(t1,t0);
/* 2^10 - 2^5 */ ecp_SqrReduce(t0,t1);
/* 2^10 - 2^0 */ ecp_MulReduce(z2_10_0,t0,z2_5_0);
/* 2^11 - 2^1 */ ecp_SqrReduce(t0,z2_10_0);
/* 2^12 - 2^2 */ ecp_SqrReduce(t1,t0);
/* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) {
ecp_SqrReduce(t0,t1);
ecp_SqrReduce(t1,t0); }
/* 2^20 - 2^0 */ ecp_MulReduce(z2_20_0,t1,z2_10_0);
/* 2^21 - 2^1 */ ecp_SqrReduce(t0,z2_20_0);
/* 2^22 - 2^2 */ ecp_SqrReduce(t1,t0);
/* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) {
ecp_SqrReduce(t0,t1);
ecp_SqrReduce(t1,t0); }
/* 2^40 - 2^0 */ ecp_MulReduce(t0,t1,z2_20_0);
/* 2^41 - 2^1 */ ecp_SqrReduce(t1,t0);
/* 2^42 - 2^2 */ ecp_SqrReduce(t0,t1);
/* 2^50 - 2^10 */ for (i = 2;i < 10;i += 2) {
ecp_SqrReduce(t1,t0);
ecp_SqrReduce(t0,t1); }
/* 2^50 - 2^0 */ ecp_MulReduce(z2_50_0,t0,z2_10_0);
/* 2^51 - 2^1 */ ecp_SqrReduce(t0,z2_50_0);
/* 2^52 - 2^2 */ ecp_SqrReduce(t1,t0);
/* 2^100 - 2^50 */ for (i = 2;i < 50;i += 2) {
ecp_SqrReduce(t0,t1);
ecp_SqrReduce(t1,t0); }
/* 2^100 - 2^0 */ ecp_MulReduce(z2_100_0,t1,z2_50_0);
/* 2^101 - 2^1 */ ecp_SqrReduce(t1,z2_100_0);
/* 2^102 - 2^2 */ ecp_SqrReduce(t0,t1);
/* 2^200 - 2^100 */ for (i = 2;i < 100;i += 2) {
ecp_SqrReduce(t1,t0);
ecp_SqrReduce(t0,t1); }
/* 2^200 - 2^0 */ ecp_MulReduce(t1,t0,z2_100_0);
/* 2^201 - 2^1 */ ecp_SqrReduce(t0,t1);
/* 2^202 - 2^2 */ ecp_SqrReduce(t1,t0);
/* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) {
ecp_SqrReduce(t0,t1);
ecp_SqrReduce(t1,t0); }
/* 2^250 - 2^0 */ ecp_MulReduce(t0,t1,z2_50_0);
/* 2^251 - 2^1 */ ecp_SqrReduce(t1,t0);
/* 2^252 - 2^2 */ ecp_SqrReduce(t0,t1);
/* 2^253 - 2^3 */ ecp_SqrReduce(t1,t0);
/* 2^254 - 2^4 */ ecp_SqrReduce(t0,t1);
/* 2^255 - 2^5 */ ecp_SqrReduce(t1,t0);
/* 2^255 - 21 */ ecp_MulReduce(out,t1,z11);
}

View File

@ -0,0 +1,175 @@
/* The MIT License (MIT)
*
* Copyright (c) 2015 mehdi sotoodeh
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef __curve25519_mehdi_h__
#define __curve25519_mehdi_h__
#ifdef __cplusplus
extern "C" {
#endif
#include "BaseTypes.h"
#define ECP_VERSION_STR "1.2.0"
#ifdef USE_ASM_LIB
#define U_WORD U64
#define S_WORD S64
#define WORDSIZE_64
#define W64(lo,hi) ((U64)hi<<32)+lo
#else
#define U_WORD U32
#define S_WORD S32
#define WORDSIZE_32
#define W64(lo,hi) lo,hi
#endif
#define K_BYTES 32
#define K_WORDS (K_BYTES/sizeof(U_WORD))
#define W256(x0,x1,x2,x3,x4,x5,x6,x7) {W64(x0,x1),W64(x2,x3),W64(x4,x5),W64(x6,x7)}
/* Affine coordinates */
typedef struct {
U_WORD x[K_WORDS];
U_WORD y[K_WORDS];
} Affine_POINT;
/* Projective coordinates */
typedef struct {
U_WORD x[K_WORDS]; /* x/z */
U_WORD y[K_WORDS]; /* y/z */
U_WORD z[K_WORDS];
U_WORD t[K_WORDS]; /* xy/z */
} Ext_POINT;
/* pre-computed, extended point */
typedef struct
{
U_WORD YpX[K_WORDS]; /* Y+X */
U_WORD YmX[K_WORDS]; /* Y-X */
U_WORD T2d[K_WORDS]; /* 2d*T */
U_WORD Z2[K_WORDS]; /* 2*Z */
} PE_POINT;
/* pre-computed, Affine point */
typedef struct
{
U_WORD YpX[K_WORDS]; /* Y+X */
U_WORD YmX[K_WORDS]; /* Y-X */
U_WORD T2d[K_WORDS]; /* 2d*T */
} PA_POINT;
typedef struct {
U_WORD bl[K_WORDS];
U_WORD zr[K_WORDS];
PE_POINT BP;
} EDP_BLINDING_CTX;
extern const U8 ecp_BasePoint[K_BYTES];
/* Return point Q = k*P */
void ecp_PointMultiply(OUT U8 *Q, IN const U8 *P, IN const U8 *K, IN int len);
/* Set low and high bits */
void ecp_TrimSecretKey(U8 *X);
/* -- utils ----------------------------------------------------------------- */
/* Convert big-endian byte array to little-endian byte array and vice versa */
U8* ecp_ReverseByteOrder(OUT U8 *Y, IN const U8 *X);
/* Convert little-endian byte array to little-endian word array */
U_WORD* ecp_BytesToWords(OUT U_WORD *Y, IN const U8 *X);
/* Convert little-endian word array to little-endian byte array */
U8* ecp_WordsToBytes(OUT U8 *Y, IN const U_WORD *X);
U8* ecp_EncodeInt(OUT U8 *Y, IN const U_WORD *X, IN U8 parity);
U8 ecp_DecodeInt(OUT U_WORD *Y, IN const U8 *X);
/* -- base point order ------------------------------------------------------ */
/* Z = (X*Y)/R mod BPO */
void eco_MontMul(OUT U_WORD *Z, IN const U_WORD *X, IN const U_WORD *Y);
/* Return Y = X*R mod BPO */
void eco_ToMont(OUT U_WORD *Y, IN const U_WORD *X);
/* Return Y = X/R mod BPO */
void eco_FromMont(OUT U_WORD *Y, IN const U_WORD *X);
/* Calculate Y = X**E mod BPO */
void eco_ExpModBPO(OUT U_WORD *Y, IN const U_WORD *X, IN const U8 *E, IN int bytes);
/* Calculate Y = 1/X mod BPO */
void eco_InvModBPO(OUT U_WORD *Y, IN const U_WORD *X);
/* Z = X*Y mod BPO */
void eco_MulReduce(OUT U_WORD *Z, IN const U_WORD *X, IN const U_WORD *Y);
/* Return Y = D mod BPO where D is 512-bit big-endian byte array (i.e SHA512 digest) */
void eco_DigestToWords( OUT U_WORD *Y, IN const U8 *D);
/* Z = X + Y mod BPO */
void eco_AddReduce(OUT U_WORD *Z, IN const U_WORD *X, IN const U_WORD *Y);
/* X mod BPO */
void eco_Mod(U_WORD *X);
#define ed25519_PackPoint(buff, Y, parity) ecp_EncodeInt(buff, Y, (U8)(parity & 1))
/* -- big-number ------------------------------------------------------------ */
U_WORD ecp_Add(U_WORD* Z, const U_WORD* X, const U_WORD* Y);
S_WORD ecp_Sub(U_WORD* Z, const U_WORD* X, const U_WORD* Y);
void ecp_SetValue(U_WORD* X, U_WORD value);
void ecp_Copy(U_WORD* Y, const U_WORD* X);
void ecp_AddReduce(U_WORD* Z, const U_WORD* X, const U_WORD* Y);
void ecp_SubReduce(U_WORD* Z, const U_WORD* X, const U_WORD* Y);
void ecp_MulReduce(U_WORD* Z, const U_WORD* X, const U_WORD* Y);
void ecp_SqrReduce(U_WORD* Y, const U_WORD* X);
void ecp_ModExp2523(U_WORD *Y, const U_WORD *X);
void ecp_Inverse(U_WORD *out, const U_WORD *z);
void ecp_MulMod(U_WORD* Z, const U_WORD* X, const U_WORD* Y);
void ecp_Mul(U_WORD* Z, const U_WORD* X, const U_WORD* Y);
/* Computes Y = b*X */
void ecp_WordMulSet(U_WORD *Y, U_WORD b, const U_WORD* X);
/* Computes Z = Y + b*X and return carry */
U_WORD ecp_WordMulAdd(U_WORD *Z, const U_WORD* Y, U_WORD b, const U_WORD* X);
/* Computes Z = Y + b*X */
void ecp_WordMulAddReduce(U_WORD *Z, const U_WORD* Y, U_WORD b, const U_WORD* X);
void ecp_Mod(U_WORD* X);
int ecp_CmpNE(const U_WORD* X, const U_WORD* Y);
int ecp_CmpLT(const U_WORD* X, const U_WORD* Y);
/* Calculate: Y = [b:X] mod BPO */
void eco_ReduceHiWord(U_WORD* Y, U_WORD b, const U_WORD* X);
/* -- ed25519 --------------------------------------------------------------- */
void ed25519_UnpackPoint(Affine_POINT *r, const unsigned char *p);
void ed25519_CalculateX(OUT U_WORD *X, IN const U_WORD *Y, U_WORD parity);
void edp_AddAffinePoint(Ext_POINT *p, const PA_POINT *q);
void edp_AddBasePoint(Ext_POINT *p);
void edp_AddPoint(Ext_POINT *r, const Ext_POINT *p, const PE_POINT *q);
void edp_DoublePoint(Ext_POINT *p);
void edp_ComputePermTable(PE_POINT *qtable, Ext_POINT *Q);
void edp_ExtPoint2PE(PE_POINT *r, const Ext_POINT *p);
void edp_BasePointMult(OUT Ext_POINT *S, IN const U_WORD *sk, IN const U_WORD *R);
void edp_BasePointMultiply(OUT Affine_POINT *Q, IN const U_WORD *sk,
IN const void *blinding);
void ecp_4Folds(U8* Y, const U_WORD* X);
void ecp_8Folds(U8* Y, const U_WORD* X);
#ifdef __cplusplus
}
#endif
#endif /* __curve25519_mehdi_h__ */

View File

@ -0,0 +1,155 @@
/* The MIT License (MIT)
*
* Copyright (c) 2015 mehdi sotoodeh
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "curve25519_mehdi.h"
/*
This library provides support for mod BPO (Base Point Order) operations
BPO = 2**252 + 27742317777372353535851937790883648493
BPO = 0x1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED
If you keep adding points together, the result repeats every BPO times.
Based on this, you may use:
public_key = (private_key mod BPO)*BasePoint
Split key example:
k1 = random()
k2 = 1/k1 mod BPO --> k1*k2 = 1 mod BPO
P1 = k1*P0 --> P2 = k2*P1 = k2*k1*P0 = P0
See selftest code for some examples of BPO usage
This library is used for implementation of EdDSA sign/verify.
*/
const U_WORD _w_NxBPO[16][K_WORDS] = { /* n*BPO */
W256(0,0,0,0,0,0,0,0),
W256(0x5CF5D3ED,0x5812631A,0xA2F79CD6,0x14DEF9DE,0,0,0,0x10000000),
W256(0xB9EBA7DA,0xB024C634,0x45EF39AC,0x29BDF3BD,0,0,0,0x20000000),
W256(0x16E17BC7,0x0837294F,0xE8E6D683,0x3E9CED9B,0,0,0,0x30000000),
W256(0x73D74FB4,0x60498C69,0x8BDE7359,0x537BE77A,0,0,0,0x40000000),
W256(0xD0CD23A1,0xB85BEF83,0x2ED6102F,0x685AE159,0,0,0,0x50000000),
W256(0x2DC2F78E,0x106E529E,0xD1CDAD06,0x7D39DB37,0,0,0,0x60000000),
W256(0x8AB8CB7B,0x6880B5B8,0x74C549DC,0x9218D516,0,0,0,0x70000000),
W256(0xE7AE9F68,0xC09318D2,0x17BCE6B2,0xA6F7CEF5,0,0,0,0x80000000),
W256(0x44A47355,0x18A57BED,0xBAB48389,0xBBD6C8D3,0,0,0,0x90000000),
W256(0xA19A4742,0x70B7DF07,0x5DAC205F,0xD0B5C2B2,0,0,0,0xA0000000),
W256(0xFE901B2F,0xC8CA4221,0x00A3BD35,0xE594BC91,0,0,0,0xB0000000),
W256(0x5B85EF1C,0x20DCA53C,0xA39B5A0C,0xFA73B66F,0,0,0,0xC0000000),
W256(0xB87BC309,0x78EF0856,0x4692F6E2,0x0F52B04E,1,0,0,0xD0000000),
W256(0x157196F6,0xD1016B71,0xE98A93B8,0x2431AA2C,1,0,0,0xE0000000),
W256(0x72676AE3,0x2913CE8B,0x8C82308F,0x3910A40B,1,0,0,0xF0000000)
};
#define minusR_0 0xCF5D3ED0
#define minusR_1 0x812631A5
#define minusR_2 0x2F79CD65
#define minusR_3 0x4DEF9DEA
#define minusR_4 1
#define minusR_5 0
#define minusR_6 0
#define minusR_7 0
/* Calculate: Y = [b:X] mod BPO
// For R = 2^256, we calculate Y = b*R + X mod BPO
// Since -R mod BPO is only 129-bits, it reduces number of multiplications if
// we calculate: Y = X - b*(-R) mod BPO instead
// Note that b*(-R) is 161-bits at most and does not need reduction.
*/
void eco_ReduceHiWord(U32* Y, U32 b, const U32* X)
{
M64 c;
U32 T[8];
/* Set T = b*(-R) */
c.u64 = (U64)b*minusR_0;
T[0] = c.u32.lo;
c.u64 = (U64)b*minusR_1 + c.u32.hi;
T[1] = c.u32.lo;
c.u64 = (U64)b*minusR_2 + c.u32.hi;
T[2] = c.u32.lo;
c.u64 = (U64)b*minusR_3 + c.u32.hi;
T[3] = c.u32.lo;
c.u64 = (U64)b + c.u32.hi;
T[4] = c.u32.lo;
T[5] = c.u32.hi;
T[6] = 0;
T[7] = 0;
/* Y = X - T */
c.s32.hi = ecp_Sub(Y, X, T);
/* Add BPO if there is a borrow */
ecp_Add(Y, Y, _w_NxBPO[c.s32.hi & 1]);
}
/* Z = X*Y mod BPO */
void eco_MulReduce(OUT U32 *Z, IN const U32 *X, IN const U32 *Y)
{
U32 T[16];
ecp_Mul(T, X, Y); /* T = X*Y */
eco_ReduceHiWord(T+7, T[15], T+7);
eco_ReduceHiWord(T+6, T[14], T+6);
eco_ReduceHiWord(T+5, T[13], T+5);
eco_ReduceHiWord(T+4, T[12], T+4);
eco_ReduceHiWord(T+3, T[11], T+3);
eco_ReduceHiWord(T+2, T[10], T+2);
eco_ReduceHiWord(T+1, T[9], T+1);
eco_ReduceHiWord(Z, T[8], T+0);
}
/* X mod BPO */
void eco_Mod(U32 *X)
{
S32 c = ecp_Sub(X, X, _w_NxBPO[X[7] >> 28]);
ecp_Add(X, X, _w_NxBPO[c & 1]);
}
/* Z = X + Y mod BPO */
void eco_AddReduce(OUT U32 *Z, IN const U32 *X, IN const U32 *Y)
{
U32 c = ecp_Add(Z, X, Y);
eco_ReduceHiWord(Z, c, Z);
}
/* Return Y = D mod BPO where D is 512-bit message digest (i.e SHA512 digest) */
void eco_DigestToWords( OUT U32 *Y, IN const U8 *md)
{
U32 T[16];
/* We use digest value as little-endian byte array. */
ecp_BytesToWords(T, md);
ecp_BytesToWords(T+8, md+32);
eco_ReduceHiWord(T+7, T[15], T+7);
eco_ReduceHiWord(T+6, T[14], T+6);
eco_ReduceHiWord(T+5, T[13], T+5);
eco_ReduceHiWord(T+4, T[12], T+4);
eco_ReduceHiWord(T+3, T[11], T+3);
eco_ReduceHiWord(T+2, T[10], T+2);
eco_ReduceHiWord(T+1, T[9], T+1);
eco_ReduceHiWord(Y, T[8], T+0);
}

View File

@ -0,0 +1,153 @@
/* The MIT License (MIT)
*
* Copyright (c) 2015 mehdi sotoodeh
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "curve25519_mehdi.h"
/* Trim private key */
void ecp_TrimSecretKey(U8 *X)
{
X[0] &= 0xf8;
X[31] = (X[31] | 0x40) & 0x7f;
}
/* Convert big-endian byte array to little-endian byte array and vice versa */
U8* ecp_ReverseByteOrder(OUT U8 *Y, IN const U8 *X)
{
int i;
for (i = 0; i < 32; i++) Y[i] = X[31-i];
return Y;
}
/* Convert little-endian byte array to little-endian word array */
U32* ecp_BytesToWords(OUT U32 *Y, IN const U8 *X)
{
int i;
M32 m;
for (i = 0; i < 8; i++)
{
m.u8.b0 = *X++;
m.u8.b1 = *X++;
m.u8.b2 = *X++;
m.u8.b3 = *X++;
Y[i] = m.u32;
}
return Y;
}
/* Convert little-endian word array to little-endian byte array */
U8* ecp_WordsToBytes(OUT U8 *Y, IN const U32 *X)
{
int i;
M32 m;
for (i = 0; i < 32;)
{
m.u32 = *X++;
Y[i++] = m.u8.b0;
Y[i++] = m.u8.b1;
Y[i++] = m.u8.b2;
Y[i++] = m.u8.b3;
}
return Y;
}
U8* ecp_EncodeInt(OUT U8 *Y, IN const U32 *X, IN U8 parity)
{
int i;
M32 m;
for (i = 0; i < 28;)
{
m.u32 = *X++;
Y[i++] = m.u8.b0;
Y[i++] = m.u8.b1;
Y[i++] = m.u8.b2;
Y[i++] = m.u8.b3;
}
m.u32 = *X;
Y[28] = m.u8.b0;
Y[29] = m.u8.b1;
Y[30] = m.u8.b2;
Y[31] = (U8)((m.u8.b3 & 0x7f) | (parity << 7));
return Y;
}
U8 ecp_DecodeInt(OUT U32 *Y, IN const U8 *X)
{
int i;
M32 m;
for (i = 0; i < 7; i++)
{
m.u8.b0 = *X++;
m.u8.b1 = *X++;
m.u8.b2 = *X++;
m.u8.b3 = *X++;
Y[i] = m.u32;
}
m.u8.b0 = *X++;
m.u8.b1 = *X++;
m.u8.b2 = *X++;
m.u8.b3 = *X & 0x7f;
Y[7] = m.u32;
return (U8)((*X >> 7) & 1);
}
void ecp_4Folds(U8* Y, const U32* X)
{
int i, j;
U8 a, b;
for (i = 32; i-- > 0; Y++)
{
a = 0;
b = 0;
for (j = 8; j > 1;)
{
j -= 2;
a = (a << 1) + ((X[j+1] >> i) & 1);
b = (b << 1) + ((X[j] >> i) & 1);
}
Y[0] = a;
Y[32] = b;
}
}
void ecp_8Folds(U8* Y, const U32* X)
{
int i, j;
U8 a = 0;
for (i = 32; i-- > 0;)
{
for (j = 8; j-- > 0;) a = (a << 1) + ((X[j] >> i) & 1);
*Y++ = a;
}
}

View File

@ -0,0 +1,27 @@
/* The MIT License (MIT)
*
* Copyright (c) 2015 mehdi sotoodeh
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "curve25519_mehdi.h"
#include "custom_blind.h"

View File

@ -0,0 +1,11 @@
EDP_BLINDING_CTX edp_custom_blinding =
{
W256(0xDB763B3D,0x2F33DD36,0xF8DC6F74,0x98C50273,0x8216E0D5,0x1AA522E6,0x6BFD2924,0x04BEFBE2),
W256(0x73FF0C54,0x459E8BA8,0x84F9550B,0xDBF5F46A,0x6DB0E537,0x65F44FFD,0x2AED4E78,0x2CF92B82),
{
W256(0xAA6B12E6,0x7E6A2126,0x2E016899,0x80453AE0,0x6F300787,0x7A7E739E,0x264D5BA3,0x4C34AA79),
W256(0x52EA83C8,0xE4156040,0x0792FA4C,0x97CA4A43,0x3C9611E1,0x2198FE81,0x41AEAAE6,0x11919AD1),
W256(0x54A55516,0xCD8ADA8A,0x820A3D65,0xF85C2DB2,0x06667F3B,0xFF1E45F7,0x26BD6148,0x7F4E9D95),
W256(0xA4986374,0x2C1BF6DE,0x57ABC779,0x7C913D04,0xFA1127FA,0x1D7E8692,0xDE0E0680,0xC636320A)
}
};

View File

@ -0,0 +1,419 @@
/* The MIT License (MIT)
*
* Copyright (c) 2015 mehdi sotoodeh
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "../include/external_calls.h"
#include "curve25519_mehdi.h"
#include "../include/ed25519_signature.h"
#include "sha512.h"
/*
* Arithmetic on twisted Edwards curve y^2 - x^2 = 1 + dx^2y^2
* with d = -(121665/121666) mod p
* d = 0x52036CEE2B6FFE738CC740797779E89800700A4D4141D8AB75EB4DCA135978A3
* p = 2**255 - 19
* p = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED
* Base point: y=4/5 mod p
* x = 0x216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A
* y = 0x6666666666666666666666666666666666666666666666666666666666666658
* Base point order:
* l = 2**252 + 27742317777372353535851937790883648493
* l = 0x1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED
*/
extern const U_WORD _w_maxP[K_WORDS];
extern const U_WORD _w_NxBPO[16][K_WORDS];
#define _w_BPO _w_NxBPO[1]
/*
// -- custom blind ---------------------------------------------------------
//
// edp_custom_blinding is defined in source/custom_blind.c
// source/custom_blind is created randomly on every new build
//
// -------------------------------------------------------------------------
*/
extern EDP_BLINDING_CTX edp_custom_blinding;
const U_WORD _w_2d[K_WORDS] = /* 2*d */
W256(0x26B2F159,0xEBD69B94,0x8283B156,0x00E0149A,0xEEF3D130,0x198E80F2,0x56DFFCE7,0x2406D9DC);
const U_WORD _w_di[K_WORDS] = /* 1/d */
W256(0xCDC9F843,0x25E0F276,0x4279542E,0x0B5DD698,0xCDB9CF66,0x2B162114,0x14D5CE43,0x40907ED2);
#include "base_folding8.h"
/*
Reference: http://eprint.iacr.org/2008/522
Cost: 7M + 7add
Return: R = P + BasePoint
*/
void edp_AddBasePoint(Ext_POINT *p)
{
U_WORD a[K_WORDS], b[K_WORDS], c[K_WORDS], d[K_WORDS], e[K_WORDS];
ecp_SubReduce(a, p->y, p->x); /* A = (Y1-X1)*(Y2-X2) */
ecp_MulReduce(a, a, _w_base_folding8[1].YmX);
ecp_AddReduce(b, p->y, p->x); /* B = (Y1+X1)*(Y2+X2) */
ecp_MulReduce(b, b, _w_base_folding8[1].YpX);
ecp_MulReduce(c, p->t, _w_base_folding8[1].T2d); /* C = T1*2d*T2 */
ecp_AddReduce(d, p->z, p->z); /* D = 2*Z1 */
ecp_SubReduce(e, b, a); /* E = B-A */
ecp_AddReduce(b, b, a); /* H = B+A */
ecp_SubReduce(a, d, c); /* F = D-C */
ecp_AddReduce(d, d, c); /* G = D+C */
ecp_MulReduce(p->x, e, a); /* E*F */
ecp_MulReduce(p->y, b, d); /* H*G */
ecp_MulReduce(p->t, e, b); /* E*H */
ecp_MulReduce(p->z, d, a); /* G*F */
}
/*
Assumptions: pre-computed q, q->Z=1
Cost: 7M + 7add
Return: P = P + Q
*/
void edp_AddAffinePoint(Ext_POINT *p, const PA_POINT *q)
{
U_WORD a[K_WORDS], b[K_WORDS], c[K_WORDS], d[K_WORDS], e[K_WORDS];
ecp_SubReduce(a, p->y, p->x); /* A = (Y1-X1)*(Y2-X2) */
ecp_MulReduce(a, a, q->YmX);
ecp_AddReduce(b, p->y, p->x); /* B = (Y1+X1)*(Y2+X2) */
ecp_MulReduce(b, b, q->YpX);
ecp_MulReduce(c, p->t, q->T2d); /* C = T1*2d*T2 */
ecp_AddReduce(d, p->z, p->z); /* D = Z1*2*Z2 (Z2=1)*/
ecp_SubReduce(e, b, a); /* E = B-A */
ecp_AddReduce(b, b, a); /* H = B+A */
ecp_SubReduce(a, d, c); /* F = D-C */
ecp_AddReduce(d, d, c); /* G = D+C */
ecp_MulReduce(p->x, e, a); /* E*F */
ecp_MulReduce(p->y, b, d); /* H*G */
ecp_MulReduce(p->t, e, b); /* E*H */
ecp_MulReduce(p->z, d, a); /* G*F */
}
/*
Reference: http://eprint.iacr.org/2008/522
Cost: 4M + 4S + 7add
Return: P = 2*P
*/
void edp_DoublePoint(Ext_POINT *p)
{
U_WORD a[K_WORDS], b[K_WORDS], c[K_WORDS], d[K_WORDS], e[K_WORDS];
ecp_SqrReduce(a, p->x); /* A = X1^2 */
ecp_SqrReduce(b, p->y); /* B = Y1^2 */
ecp_SqrReduce(c, p->z); /* C = 2*Z1^2 */
ecp_AddReduce(c, c, c);
ecp_SubReduce(d, _w_maxP, a); /* D = -A */
ecp_SubReduce(a, d, b); /* H = D-B */
ecp_AddReduce(d, d, b); /* G = D+B */
ecp_SubReduce(b, d, c); /* F = G-C */
ecp_AddReduce(e, p->x, p->y); /* E = (X1+Y1)^2-A-B = (X1+Y1)^2+H */
ecp_SqrReduce(e, e);
ecp_AddReduce(e, e, a);
ecp_MulReduce(p->x, e, b); /* E*F */
ecp_MulReduce(p->y, a, d); /* H*G */
ecp_MulReduce(p->z, d, b); /* G*F */
ecp_MulReduce(p->t, e, a); /* E*H */
}
/* -- FOLDING ---------------------------------------------------------------
//
// The performance boost is achieved by a process that I call it FOLDING.
// Folding can be viewed as an extension of Shamir's trick but it is based
// on break down of the scalar multiplier of a*P into a polynomial of the
// form:
//
// a*P = SUM(a_i*2^(i*w))*P for i = 0,1,2,...n-1
//
// a*P = SUM(a_i*P_i)
//
// where P_i = (2^(i*w))*P
// n = number of folds
// w = bit-length of a_i
//
// For folding of 8, 256-bit multiplier 'a' is chopped into 8 limbs of
// 32-bits each (a_0, a_1,...a_7). P_0 - P_7 can be pre-calculated and
// their 256-different permutations can be cached or hard-coded
// directly into the code.
// This arrangement combined with double-and-add approach reduces the
// number of EC point calculations by a factor of 8. We only need 31
// double & add operations.
//
// +---+---+---+---+---+---+- .... -+---+---+---+---+---+---+
// a = (|255|254|253|252|251|250| | 5 | 4 | 3 | 2 | 1 | 0 |)
// +---+---+---+---+---+---+- .... -+---+---+---+---+---+---+
//
// a_i P_i
// +---+---+---+ .... -+---+---+---+ ----------
// a7 = (|255|254|253| |226|225|224|) * (2**224)*P
// +---+---+---+ .... -+---+---+---+
// a6 = (|225|224|223| |194|193|192|) * (2**192)*P
// +---+---+---+ .... -+---+---+---+
// a5 = (|191|190|189| |162|161|160|) * (2**160)*P
// +---+---+---+ .... -+---+---+---+
// a4 = (|159|158|157| |130|129|128|) * (2**128)*P
// +---+---+---+ .... -+---+---+---+
// a3 = (|127|126|125| | 98| 97| 96|) * (2**96)*P
// +---+---+---+ .... -+---+---+---+
// a2 = (| 95| 94| 93| | 66| 65| 64|) * (2**64)*P
// +---+---+---+ .... -+---+---+---+
// a1 = (| 63| 62| 61| | 34| 33| 32|) * (2**32)*P
// +---+---+---+ .... -+---+---+---+
// a0 = (| 31| 30| 29| | 2 | 1 | 0 |) * (2**0)*P
// +---+---+---+ .... -+---+---+---+
// | | | |
// | +--+ | +--+
// | | | |
// V V slices V V
// +---+ +---+ .... +---+ +---+
// |255| |254| |225| |224| P7
// +---+ +---+ .... +---+ +---+
// |225| |224| |193| |192| P6
// +---+ +---+ .... +---+ +---+
// |191| |190| |161| |160| P5
// +---+ +---+ .... +---+ +---+
// |159| |158| |129| |128| P4
// +---+ +---+ .... +---+ +---+
// |127| |126| | 97| | 96| P3
// +---+ +---+ .... +---+ +---+
// | 95| | 94| | 65| | 64| P2
// +---+ +---+ .... +---+ +---+
// | 63| | 62| | 33| | 32| P1
// +---+ +---+ .... +---+ +---+
// | 31| | 30| | 1 | | 0 | P0
// +---+ +---+ .... +---+ +---+
// cut[]: 0 1 .... 30 31
// --------------------------------------------------------------------------
// Return S = a*P where P is ed25519 base point and R is random
*/
void edp_BasePointMult(
OUT Ext_POINT *S,
IN const U_WORD *sk,
IN const U_WORD *R)
{
int i = 1;
U8 cut[32];
const PA_POINT *p0;
ecp_8Folds(cut, sk);
p0 = &_w_base_folding8[cut[0]];
ecp_SubReduce(S->x, p0->YpX, p0->YmX); /* 2x */
ecp_AddReduce(S->y, p0->YpX, p0->YmX); /* 2y */
ecp_MulReduce(S->t, p0->T2d, _w_di); /* 2xy */
/* Randomize starting point */
ecp_AddReduce(S->z, R, R); /* Z = 2R */
ecp_MulReduce(S->x, S->x, R); /* X = 2xR */
ecp_MulReduce(S->t, S->t, R); /* T = 2xyR */
ecp_MulReduce(S->y, S->y, R); /* Y = 2yR */
do
{
edp_DoublePoint(S);
edp_AddAffinePoint(S, &_w_base_folding8[cut[i]]);
} while (i++ < 31);
}
void edp_BasePointMultiply(
OUT Affine_POINT *R,
IN const U_WORD *sk,
IN const void *blinding)
{
Ext_POINT S;
U_WORD t[K_WORDS];
if (blinding)
{
eco_AddReduce(t, sk, ((EDP_BLINDING_CTX*)blinding)->bl);
edp_BasePointMult(&S, t, ((EDP_BLINDING_CTX*)blinding)->zr);
edp_AddPoint(&S, &S, &((EDP_BLINDING_CTX*)blinding)->BP);
}
else
{
edp_BasePointMult(&S, sk, edp_custom_blinding.zr);
}
ecp_Inverse(S.z, S.z);
ecp_MulMod(R->x, S.x, S.z);
ecp_MulMod(R->y, S.y, S.z);
}
void edp_ExtPoint2PE(PE_POINT *r, const Ext_POINT *p)
{
ecp_AddReduce(r->YpX, p->y, p->x);
ecp_SubReduce(r->YmX, p->y, p->x);
ecp_MulReduce(r->T2d, p->t, _w_2d);
ecp_AddReduce(r->Z2, p->z, p->z);
}
/* -- Blinding -------------------------------------------------------------
//
// Blinding is a measure to protect against side channel attacks.
// Blinding randomizes the scalar multiplier.
//
// Instead of calculating a*P, calculate (a+b mod BPO)*P + B
//
// Where b = random blinding and B = -b*P
//
// -------------------------------------------------------------------------
*/
void *ed25519_Blinding_Init(
void *context, /* IO: null or ptr blinding context */
const unsigned char *seed, /* IN: [size bytes] random blinding seed */
size_t size) /* IN: size of blinding seed */
{
struct {
Ext_POINT T;
U_WORD t[K_WORDS];
SHA512_CTX H;
U8 digest[SHA512_DIGEST_LENGTH];
} d;
EDP_BLINDING_CTX *ctx = (EDP_BLINDING_CTX*)context;
if (ctx == 0)
{
ctx = (EDP_BLINDING_CTX*)mem_alloc(sizeof(EDP_BLINDING_CTX));
if (ctx == 0) return 0;
}
/* Use edp_custom_blinding to protect generation of the new blinder */
SHA512_Init(&d.H);
SHA512_Update(&d.H, edp_custom_blinding.zr, 32);
SHA512_Update(&d.H, seed, size);
SHA512_Final(d.digest, &d.H);
ecp_BytesToWords(ctx->zr, d.digest+32);
ecp_BytesToWords(d.t, d.digest);
eco_Mod(d.t);
ecp_Sub(ctx->bl, _w_BPO, d.t);
eco_AddReduce(d.t, d.t, edp_custom_blinding.bl);
edp_BasePointMult(&d.T, d.t, edp_custom_blinding.zr);
edp_AddPoint(&d.T, &d.T, &edp_custom_blinding.BP);
edp_ExtPoint2PE(&ctx->BP, &d.T);
/* clear potentially sensitive data */
mem_clear (&d, sizeof(d));
return ctx;
}
void ed25519_Blinding_Finish(
void *context) /* IN: blinding context */
{
if (context)
{
mem_clear (context, sizeof(EDP_BLINDING_CTX));
mem_free (context);
}
}
/* Generate public and private key pair associated with the secret key */
void ed25519_CreateKeyPair(
unsigned char *pubKey, /* OUT: public key */
unsigned char *privKey, /* OUT: private key */
const void *blinding, /* IN: [optional] null or blinding context */
const unsigned char *sk) /* IN: secret key (32 bytes) */
{
U8 md[SHA512_DIGEST_LENGTH];
U_WORD t[K_WORDS];
SHA512_CTX H;
Affine_POINT Q;
/* [a:b] = H(sk) */
SHA512_Init(&H);
SHA512_Update(&H, sk, 32);
SHA512_Final(md, &H);
ecp_TrimSecretKey(md);
ecp_BytesToWords(t, md);
edp_BasePointMultiply(&Q, t, blinding);
ed25519_PackPoint(pubKey, Q.y, Q.x[0]);
memcpy(privKey, sk, 32);
memcpy(privKey+32, pubKey, 32);
}
/*
* Generate message signature
*/
void ed25519_SignMessage(
unsigned char *signature, /* OUT: [64 bytes] signature (R,S) */
const unsigned char *privKey, /* IN: [64 bytes] private key (sk,pk) */
const void *blinding, /* IN: [optional] null or blinding context */
const unsigned char *msg, /* IN: [msg_size bytes] message to sign */
size_t msg_size)
{
SHA512_CTX H;
Affine_POINT R;
U_WORD a[K_WORDS], t[K_WORDS], r[K_WORDS];
U8 md[SHA512_DIGEST_LENGTH];
/* [a:b] = H(sk) */
SHA512_Init(&H);
SHA512_Update(&H, privKey, 32);
SHA512_Final(md, &H);
ecp_TrimSecretKey(md); /* a = first 32 bytes */
ecp_BytesToWords(a, md);
/* r = H(b + m) mod BPO */
SHA512_Init(&H);
SHA512_Update(&H, md+32, 32);
SHA512_Update(&H, msg, msg_size);
SHA512_Final(md, &H);
eco_DigestToWords(r, md);
eco_Mod(r); /* r mod BPO */
/* R = r*P */
edp_BasePointMultiply(&R, r, blinding);
ed25519_PackPoint(signature, R.y, R.x[0]); /* R part of signature */
/* S = r + H(encoded(R) + pk + m) * a mod BPO */
SHA512_Init(&H);
SHA512_Update(&H, signature, 32); /* encoded(R) */
SHA512_Update(&H, privKey+32, 32); /* pk */
SHA512_Update(&H, msg, msg_size); /* m */
SHA512_Final(md, &H);
eco_DigestToWords(t, md);
eco_MulReduce(t, t, a); /* h()*a */
eco_AddReduce(t, t, r);
eco_Mod(t);
ecp_WordsToBytes(signature+32, t); /* S part of signature */
/* Clear sensitive data */
ecp_SetValue(a, 0);
ecp_SetValue(r, 0);
}

View File

@ -0,0 +1,313 @@
/* The MIT License (MIT)
*
* Copyright (c) 2015 mehdi sotoodeh
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "../include/external_calls.h"
#include "curve25519_mehdi.h"
#include "../include/ed25519_signature.h"
#include "sha512.h"
/*
* Arithmetic on twisted Edwards curve y^2 - x^2 = 1 + dx^2y^2
* with d = -(121665/121666) mod p
* d = 0x52036CEE2B6FFE738CC740797779E89800700A4D4141D8AB75EB4DCA135978A3
* p = 2**255 - 19
* p = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED
* Base point: y=4/5 mod p
* x = 0x216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A
* y = 0x6666666666666666666666666666666666666666666666666666666666666658
* Base point order:
* l = 2**252 + 27742317777372353535851937790883648493
* l = 0x1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED
*/
typedef struct {
unsigned char pk[32];
PE_POINT q_table[16];
} EDP_SIGV_CTX;
extern const U_WORD _w_P[K_WORDS];
extern const U_WORD _w_di[K_WORDS];
extern const PA_POINT _w_base_folding8[256];
extern const U_WORD _w_NxBPO[16][K_WORDS];
#define _w_BPO _w_NxBPO[1]
#define _w_Zero _w_base_folding8[0].T2d
#define _w_One _w_base_folding8[0].YpX
const U_WORD _w_I[K_WORDS] = /* sqrt(-1) */
W256(0x4A0EA0B0,0xC4EE1B27,0xAD2FE478,0x2F431806,0x3DFBD7A7,0x2B4D0099,0x4FC1DF0B,0x2B832480);
static const U_WORD _w_d[K_WORDS] =
W256(0x135978A3,0x75EB4DCA,0x4141D8AB,0x00700A4D,0x7779E898,0x8CC74079,0x2B6FFE73,0x52036CEE);
void ed25519_CalculateX(OUT U_WORD *X, IN const U_WORD *Y, U_WORD parity)
{
U_WORD u[K_WORDS], v[K_WORDS], a[K_WORDS], b[K_WORDS];
/* Calculate sqrt((y^2 - 1)/(d*y^2 + 1)) */
ecp_SqrReduce(u, Y); /* u = y^2 */
ecp_MulReduce(v, u, _w_d); /* v = dy^2 */
ecp_SubReduce(u, u, _w_One); /* u = y^2-1 */
ecp_AddReduce(v, v, _w_One); /* v = dy^2+1 */
/* Calculate: sqrt(u/v) = u*v^3 * (u*v^7)^((p-5)/8) */
ecp_SqrReduce(b, v);
ecp_MulReduce(a, u, b);
ecp_MulReduce(a, a, v); /* a = u*v^3 */
ecp_SqrReduce(b, b); /* b = v^4 */
ecp_MulReduce(b, a, b); /* b = u*v^7 */
ecp_ModExp2523(b, b);
ecp_MulReduce(X, b, a);
/* Check if we have correct sqrt, else, multiply by sqrt(-1) */
ecp_SqrReduce(b, X);
ecp_MulReduce(b, b, v);
ecp_SubReduce(b, b, u);
ecp_Mod(b);
if (ecp_CmpNE(b, _w_Zero)) ecp_MulReduce(X, X, _w_I);
while (ecp_CmpLT(X, _w_P) == 0) ecp_Sub(X, X, _w_P);
/* match parity */
if (((X[0] ^ parity) & 1) != 0)
ecp_Sub(X, _w_P, X);
}
void ed25519_UnpackPoint(Affine_POINT *r, const unsigned char *p)
{
U8 parity = ecp_DecodeInt(r->y, p);
ed25519_CalculateX(r->x, r->y, parity);
}
void ecp_SrqMulReduce(U_WORD *Z, const U_WORD *X, int n, const U_WORD *Y)
{
U_WORD t[K_WORDS];
ecp_SqrReduce(t, X);
while (n-- > 1) ecp_SqrReduce(t, t);
ecp_MulReduce(Z, t, Y);
}
void ecp_ModExp2523(U_WORD *Y, const U_WORD *X)
{
U_WORD x2[K_WORDS], x9[K_WORDS], x11[K_WORDS], x5[K_WORDS], x10[K_WORDS];
U_WORD x20[K_WORDS], x50[K_WORDS], x100[K_WORDS], t[K_WORDS];
ecp_SqrReduce(x2, X); /* 2 */
ecp_SrqMulReduce(x9, x2, 2, X); /* 9 */
ecp_MulReduce(x11, x9, x2); /* 11 */
ecp_SqrReduce(t, x11); /* 22 */
ecp_MulReduce(x5, t, x9); /* 31 = 2^5 - 2^0 */
ecp_SrqMulReduce(x10, x5, 5, x5); /* 2^10 - 2^0 */
ecp_SrqMulReduce(x20, x10, 10, x10); /* 2^20 - 2^0 */
ecp_SrqMulReduce(t, x20, 20, x20); /* 2^40 - 2^0 */
ecp_SrqMulReduce(x50, t, 10, x10); /* 2^50 - 2^0 */
ecp_SrqMulReduce(x100, x50, 50, x50); /* 2^100 - 2^0 */
ecp_SrqMulReduce(t, x100, 100, x100); /* 2^200 - 2^0 */
ecp_SrqMulReduce(t, t, 50, x50); /* 2^250 - 2^0 */
ecp_SqrReduce(t, t); ecp_SqrReduce(t, t); /* 2^252 - 2^2 */
ecp_MulReduce(Y, t, X); /* 2^252 - 3 */
}
/*
Assumptions: pre-computed q
Cost: 8M + 6add
Return: P = P + Q
*/
void edp_AddPoint(Ext_POINT *r, const Ext_POINT *p, const PE_POINT *q)
{
U_WORD a[K_WORDS], b[K_WORDS], c[K_WORDS], d[K_WORDS], e[K_WORDS];
ecp_SubReduce(a, p->y, p->x); /* A = (Y1-X1)*(Y2-X2) */
ecp_MulReduce(a, a, q->YmX);
ecp_AddReduce(b, p->y, p->x); /* B = (Y1+X1)*(Y2+X2) */
ecp_MulReduce(b, b, q->YpX);
ecp_MulReduce(c, p->t, q->T2d); /* C = T1*2d*T2 */
ecp_MulReduce(d, p->z, q->Z2); /* D = Z1*2*Z2 */
ecp_SubReduce(e, b, a); /* E = B-A */
ecp_AddReduce(b, b, a); /* H = B+A */
ecp_SubReduce(a, d, c); /* F = D-C */
ecp_AddReduce(d, d, c); /* G = D+C */
ecp_MulReduce(r->x, e, a); /* E*F */
ecp_MulReduce(r->y, b, d); /* H*G */
ecp_MulReduce(r->t, e, b); /* E*H */
ecp_MulReduce(r->z, d, a); /* G*F */
}
int ed25519_VerifySignature(
const unsigned char *signature, /* IN: signature (R,S) */
const unsigned char *publicKey, /* IN: public key */
const unsigned char *msg, size_t msg_size) /* IN: message to sign */
{
EDP_SIGV_CTX ctx;
ed25519_Verify_Init(&ctx, publicKey);
return ed25519_Verify_Check(&ctx, signature, msg, msg_size);
}
#define QTABLE_SET(d,s) \
edp_AddPoint(&T, &Q, &ctx->q_table[s]); \
edp_ExtPoint2PE(&ctx->q_table[d], &T)
void * ed25519_Verify_Init(
void *context, /* IO: null or context buffer to use */
const unsigned char *publicKey) /* IN: [32 bytes] public key */
{
int i;
Ext_POINT Q, T;
EDP_SIGV_CTX *ctx = (EDP_SIGV_CTX*)context;
if (ctx == 0) ctx = (EDP_SIGV_CTX*)mem_alloc(sizeof(EDP_SIGV_CTX));
if (ctx)
{
memcpy(ctx->pk, publicKey, 32);
i = ecp_DecodeInt(Q.y, publicKey);
ed25519_CalculateX(Q.x, Q.y, ~i); /* Invert parity for -Q */
ecp_MulMod(Q.t, Q.x, Q.y);
ecp_SetValue(Q.z, 1);
/* pre-compute q-table */
/* Calculate: Q0=Q, Q1=(2^64)*Q, Q2=(2^128)*Q, Q3=(2^192)*Q */
ecp_SetValue(ctx->q_table[0].YpX, 1); /* -- -- -- -- */
ecp_SetValue(ctx->q_table[0].YmX, 1);
ecp_SetValue(ctx->q_table[0].T2d, 0);
ecp_SetValue(ctx->q_table[0].Z2, 2);
edp_ExtPoint2PE(&ctx->q_table[1], &Q); /* -- -- -- q0 */
for (i = 0; i < 64; i++) edp_DoublePoint(&Q);
edp_ExtPoint2PE(&ctx->q_table[2], &Q); /* -- -- q1 -- */
QTABLE_SET(3,1); /* -- -- q1 q0 */
do edp_DoublePoint(&Q); while (++i < 128);
edp_ExtPoint2PE(&ctx->q_table[4], &Q); /* -- q2 -- -- */
QTABLE_SET(5, 1); /* -- q2 -- q0 */
QTABLE_SET(6, 2); /* -- q2 q1 -- */
QTABLE_SET(7, 3); /* -- q2 q1 q0 */
do edp_DoublePoint(&Q); while (++i < 192);
edp_ExtPoint2PE(&ctx->q_table[8], &Q); /* q3 -- -- -- */
QTABLE_SET(9, 1); /* q3 -- -- q0 */
QTABLE_SET(10, 2); /* q3 -- q1 -- */
QTABLE_SET(11, 3); /* q3 -- q1 q0 */
QTABLE_SET(12, 4); /* q3 q2 -- -- */
QTABLE_SET(13, 5); /* q3 q2 -- q0 */
QTABLE_SET(14, 6); /* q3 q2 q1 -- */
QTABLE_SET(15, 7); /* q3 q2 q1 q0 */
}
return ctx;
}
void ed25519_Verify_Finish(void *ctx)
{
mem_free(ctx);
}
/*
Assumptions: qtable = pre-computed Q
Calculate: point R = a*P + b*Q where P is base point
*/
static void edp_PolyPointMultiply(
Affine_POINT *r,
const U_WORD *a,
const U_WORD *b,
const PE_POINT *qtable)
{
int i = 1;
Ext_POINT S;
const PE_POINT *q0;
U8 u[32], v[64];
ecp_8Folds(u, a);
ecp_4Folds(v, b);
/* Set initial value of S */
q0 = &qtable[v[0]];
ecp_SubReduce(S.x, q0->YpX, q0->YmX); /* 2x */
ecp_AddReduce(S.y, q0->YpX, q0->YmX); /* 2y */
ecp_MulReduce(S.t, q0->T2d, _w_di); /* 2xy */
ecp_Copy(S.z, q0->Z2); /* 2z */
do
{ /* 31D + 31A */
edp_DoublePoint(&S);
edp_AddPoint(&S, &S, &qtable[v[i]]);
} while (++i < 32);
do
{ /* 32D + 64A */
edp_DoublePoint(&S);
edp_AddAffinePoint(&S, &_w_base_folding8[u[i-32]]);
edp_AddPoint(&S, &S, &qtable[v[i]]);
} while (++i < 64);
ecp_Inverse(S.z, S.z);
ecp_MulMod(r->x, S.x, S.z);
ecp_MulMod(r->y, S.y, S.z);
}
/*
This function can be used for batch verification.
Assumptions: context = ed25519_Verify_Init(pk)
*/
int ed25519_Verify_Check(
const void *context, /* IN: precomputes */
const unsigned char *signature, /* IN: signature (R,S) */
const unsigned char *msg, size_t msg_size) /* IN: message to sign */
{
SHA512_CTX H;
Affine_POINT T;
U_WORD h[K_WORDS], s[K_WORDS];
U8 md[SHA512_DIGEST_LENGTH];
/* h = H(enc(R) + pk + m) mod BPO */
SHA512_Init(&H);
SHA512_Update(&H, signature, 32); /* enc(R) */
SHA512_Update(&H, ((EDP_SIGV_CTX*)context)->pk, 32);
SHA512_Update(&H, msg, msg_size);
SHA512_Final(md, &H);
eco_DigestToWords(h, md);
eco_Mod(h);
/* T = s*P + h*(-Q) = (s - h*a)*P = r*P = R */
ecp_BytesToWords(s, signature+32);
edp_PolyPointMultiply(&T, s, h, ((EDP_SIGV_CTX*)context)->q_table);
ed25519_PackPoint(md, T.y, T.x[0]);
return (memcmp(md, signature, 32) == 0) ? 1 : 0;
}

294
source/extern/curve25519/source/sha512.c vendored Normal file
View File

@ -0,0 +1,294 @@
/* crypto/sha/sha512.c */
/* ====================================================================
* Copyright (c) 2004 The OpenSSL Project. All rights reserved
* according to the OpenSSL license [found in ../../LICENSE].
* ====================================================================
*/
/*
* IMPLEMENTATION NOTES.
*
* As you might have noticed 32-bit hash algorithms:
*
* - permit SHA_LONG to be wider than 32-bit (case on CRAY);
* - optimized versions implement two transform functions: one operating
* on [aligned] data in host byte order and one - on data in input
* stream byte order;
* - share common byte-order neutral collector and padding function
* implementations, ../md32_common.h;
*
* Neither of the above applies to this SHA-512 implementations. Reasons
* [in reverse order] are:
*
* - it's the only 64-bit hash algorithm for the moment of this writing,
* there is no need for common collector/padding implementation [yet];
* - by supporting only one transform function [which operates on
* *aligned* data in input stream byte order, big-endian in this case]
* we minimize burden of maintenance in two ways: a) collector/padding
* function is simpler; b) only one transform function to stare at;
* - SHA_LONG64 is required to be exactly 64-bit in order to be able to
* apply a number of optimizations to mitigate potential performance
* penalties caused by previous design decision;
*
* Caveat lector.
*
* Implementation relies on the fact that "long long" is 64-bit on
* both 32- and 64-bit platforms. If some compiler vendor comes up
* with 128-bit long long, adjustment to sha.h would be required.
* As this implementation relies on 64-bit integer type, it's totally
* inappropriate for platforms which don't support it, most notably
* 16-bit platforms.
* <appro@fy.chalmers.se>
*/
#include <string.h>
#include "../include/external_calls.h"
#include "sha512.h"
#define UINT64(X) X##ULL
void SHA512_Transform (SHA512_CTX *ctx, const void *in);
void SHA512_Init (SHA512_CTX *c)
{
c->h[0]=UINT64(0x6a09e667f3bcc908);
c->h[1]=UINT64(0xbb67ae8584caa73b);
c->h[2]=UINT64(0x3c6ef372fe94f82b);
c->h[3]=UINT64(0xa54ff53a5f1d36f1);
c->h[4]=UINT64(0x510e527fade682d1);
c->h[5]=UINT64(0x9b05688c2b3e6c1f);
c->h[6]=UINT64(0x1f83d9abfb41bd6b);
c->h[7]=UINT64(0x5be0cd19137e2179);
c->Nl=0;
c->Nh=0;
c->num=0;
c->md_len=SHA512_DIGEST_LENGTH;
}
void SHA512_Final (unsigned char *md, SHA512_CTX *c)
{
unsigned char *p=(unsigned char *)c->u.p;
size_t n=c->num;
p[n]=0x80; /* There always is a room for one */
n++;
if (n > (SHA512_CBLOCK-16))
mem_fill (p+n,0,SHA512_CBLOCK-n), n=0,
SHA512_Transform (c,p);
mem_fill (p+n,0,SHA512_CBLOCK-16-n);
#ifdef ECP_CONFIG_BIG_ENDIAN
c->u.d[SHA_LBLOCK-2] = c->Nh;
c->u.d[SHA_LBLOCK-1] = c->Nl;
#else
p[SHA512_CBLOCK-1] = (unsigned char)(c->Nl);
p[SHA512_CBLOCK-2] = (unsigned char)(c->Nl>>8);
p[SHA512_CBLOCK-3] = (unsigned char)(c->Nl>>16);
p[SHA512_CBLOCK-4] = (unsigned char)(c->Nl>>24);
p[SHA512_CBLOCK-5] = (unsigned char)(c->Nl>>32);
p[SHA512_CBLOCK-6] = (unsigned char)(c->Nl>>40);
p[SHA512_CBLOCK-7] = (unsigned char)(c->Nl>>48);
p[SHA512_CBLOCK-8] = (unsigned char)(c->Nl>>56);
p[SHA512_CBLOCK-9] = (unsigned char)(c->Nh);
p[SHA512_CBLOCK-10] = (unsigned char)(c->Nh>>8);
p[SHA512_CBLOCK-11] = (unsigned char)(c->Nh>>16);
p[SHA512_CBLOCK-12] = (unsigned char)(c->Nh>>24);
p[SHA512_CBLOCK-13] = (unsigned char)(c->Nh>>32);
p[SHA512_CBLOCK-14] = (unsigned char)(c->Nh>>40);
p[SHA512_CBLOCK-15] = (unsigned char)(c->Nh>>48);
p[SHA512_CBLOCK-16] = (unsigned char)(c->Nh>>56);
#endif
SHA512_Transform (c,p);
if (md) for (n=0; n < SHA512_DIGEST_LENGTH/8; n++)
{
M64 m;
m.u64 = c->h[n];
*(md++) = m.u8.b7;
*(md++) = m.u8.b6;
*(md++) = m.u8.b5;
*(md++) = m.u8.b4;
*(md++) = m.u8.b3;
*(md++) = m.u8.b2;
*(md++) = m.u8.b1;
*(md++) = m.u8.b0;
}
}
void SHA512_Update (SHA512_CTX *c, const void *_data, size_t len)
{
SHA_LONG64 l;
unsigned char *p=c->u.p;
const unsigned char *data=(const unsigned char *)_data;
if (len==0) return;
l = (c->Nl+(((SHA_LONG64)len)<<3))&UINT64(0xffffffffffffffff);
if (l < c->Nl) c->Nh++;
if (sizeof(len)>=8) c->Nh+=(((SHA_LONG64)len)>>61);
c->Nl=l;
if (c->num != 0)
{
size_t n = SHA512_CBLOCK - c->num;
if (len < n)
{
memcpy (p+c->num,data,len), c->num += (unsigned int)len;
return;
}
else
{
memcpy (p+c->num,data,n), c->num = 0;
len-=n, data+=n;
SHA512_Transform (c,p);
}
}
while (len >= SHA512_CBLOCK)
{
SHA512_Transform (c,data);//,len/SHA512_CBLOCK),
data += SHA512_CBLOCK;
len -= SHA512_CBLOCK;
}
if (len != 0)
memcpy (p,data,len), c->num = (int)len;
}
static const SHA_LONG64 K512[80] =
{
UINT64(0x428a2f98d728ae22),UINT64(0x7137449123ef65cd),
UINT64(0xb5c0fbcfec4d3b2f),UINT64(0xe9b5dba58189dbbc),
UINT64(0x3956c25bf348b538),UINT64(0x59f111f1b605d019),
UINT64(0x923f82a4af194f9b),UINT64(0xab1c5ed5da6d8118),
UINT64(0xd807aa98a3030242),UINT64(0x12835b0145706fbe),
UINT64(0x243185be4ee4b28c),UINT64(0x550c7dc3d5ffb4e2),
UINT64(0x72be5d74f27b896f),UINT64(0x80deb1fe3b1696b1),
UINT64(0x9bdc06a725c71235),UINT64(0xc19bf174cf692694),
UINT64(0xe49b69c19ef14ad2),UINT64(0xefbe4786384f25e3),
UINT64(0x0fc19dc68b8cd5b5),UINT64(0x240ca1cc77ac9c65),
UINT64(0x2de92c6f592b0275),UINT64(0x4a7484aa6ea6e483),
UINT64(0x5cb0a9dcbd41fbd4),UINT64(0x76f988da831153b5),
UINT64(0x983e5152ee66dfab),UINT64(0xa831c66d2db43210),
UINT64(0xb00327c898fb213f),UINT64(0xbf597fc7beef0ee4),
UINT64(0xc6e00bf33da88fc2),UINT64(0xd5a79147930aa725),
UINT64(0x06ca6351e003826f),UINT64(0x142929670a0e6e70),
UINT64(0x27b70a8546d22ffc),UINT64(0x2e1b21385c26c926),
UINT64(0x4d2c6dfc5ac42aed),UINT64(0x53380d139d95b3df),
UINT64(0x650a73548baf63de),UINT64(0x766a0abb3c77b2a8),
UINT64(0x81c2c92e47edaee6),UINT64(0x92722c851482353b),
UINT64(0xa2bfe8a14cf10364),UINT64(0xa81a664bbc423001),
UINT64(0xc24b8b70d0f89791),UINT64(0xc76c51a30654be30),
UINT64(0xd192e819d6ef5218),UINT64(0xd69906245565a910),
UINT64(0xf40e35855771202a),UINT64(0x106aa07032bbd1b8),
UINT64(0x19a4c116b8d2d0c8),UINT64(0x1e376c085141ab53),
UINT64(0x2748774cdf8eeb99),UINT64(0x34b0bcb5e19b48a8),
UINT64(0x391c0cb3c5c95a63),UINT64(0x4ed8aa4ae3418acb),
UINT64(0x5b9cca4f7763e373),UINT64(0x682e6ff3d6b2b8a3),
UINT64(0x748f82ee5defb2fc),UINT64(0x78a5636f43172f60),
UINT64(0x84c87814a1f0ab72),UINT64(0x8cc702081a6439ec),
UINT64(0x90befffa23631e28),UINT64(0xa4506cebde82bde9),
UINT64(0xbef9a3f7b2c67915),UINT64(0xc67178f2e372532b),
UINT64(0xca273eceea26619c),UINT64(0xd186b8c721c0c207),
UINT64(0xeada7dd6cde0eb1e),UINT64(0xf57d4f7fee6ed178),
UINT64(0x06f067aa72176fba),UINT64(0x0a637dc5a2c898a6),
UINT64(0x113f9804bef90dae),UINT64(0x1b710b35131c471b),
UINT64(0x28db77f523047d84),UINT64(0x32caab7b40c72493),
UINT64(0x3c9ebe0a15c9bebc),UINT64(0x431d67c49c100d4c),
UINT64(0x4cc5d4becb3e42b6),UINT64(0x597f299cfc657e2a),
UINT64(0x5fcb6fab3ad6faec),UINT64(0x6c44198c4a475817)
};
#define B(x,j) (((SHA_LONG64)(*(((const unsigned char *)(&x))+j)))<<((7-j)*8))
#define PULL64(x) (B(x,0)|B(x,1)|B(x,2)|B(x,3)|B(x,4)|B(x,5)|B(x,6)|B(x,7))
#define ROTR(x,s) (((x)>>s) | (x)<<(64-s))
#define Sigma0(x) (ROTR((x),28) ^ ROTR((x),34) ^ ROTR((x),39))
#define Sigma1(x) (ROTR((x),14) ^ ROTR((x),18) ^ ROTR((x),41))
#define sigma0(x) (ROTR((x),1) ^ ROTR((x),8) ^ ((x)>>7))
#define sigma1(x) (ROTR((x),19) ^ ROTR((x),61) ^ ((x)>>6))
#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z)))
#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
#define ROUND_00_15(i,a,b,c,d,e,f,g,h) do { \
T1 += h + Sigma1(e) + Ch(e,f,g) + K512[i]; \
h = Sigma0(a) + Maj(a,b,c); \
d += T1; h += T1; } while (0)
#define ROUND_16_80(i,j,a,b,c,d,e,f,g,h,X) do { \
s0 = X[(j+1)&0x0f]; s0 = sigma0(s0); \
s1 = X[(j+14)&0x0f]; s1 = sigma1(s1); \
T1 = X[(j)&0x0f] += s0 + s1 + X[(j+9)&0x0f]; \
ROUND_00_15(i+j,a,b,c,d,e,f,g,h); } while (0)
void SHA512_Transform (SHA512_CTX *ctx, const void *in)
{
const SHA_LONG64 *W = (SHA_LONG64*)in;
SHA_LONG64 a,b,c,d,e,f,g,h,s0,s1,T1;
SHA_LONG64 X[16];
int i;
a = ctx->h[0]; b = ctx->h[1]; c = ctx->h[2]; d = ctx->h[3];
e = ctx->h[4]; f = ctx->h[5]; g = ctx->h[6]; h = ctx->h[7];
#ifdef ECP_CONFIG_BIG_ENDIAN
T1 = X[0] = W[0]; ROUND_00_15(0,a,b,c,d,e,f,g,h);
T1 = X[1] = W[1]; ROUND_00_15(1,h,a,b,c,d,e,f,g);
T1 = X[2] = W[2]; ROUND_00_15(2,g,h,a,b,c,d,e,f);
T1 = X[3] = W[3]; ROUND_00_15(3,f,g,h,a,b,c,d,e);
T1 = X[4] = W[4]; ROUND_00_15(4,e,f,g,h,a,b,c,d);
T1 = X[5] = W[5]; ROUND_00_15(5,d,e,f,g,h,a,b,c);
T1 = X[6] = W[6]; ROUND_00_15(6,c,d,e,f,g,h,a,b);
T1 = X[7] = W[7]; ROUND_00_15(7,b,c,d,e,f,g,h,a);
T1 = X[8] = W[8]; ROUND_00_15(8,a,b,c,d,e,f,g,h);
T1 = X[9] = W[9]; ROUND_00_15(9,h,a,b,c,d,e,f,g);
T1 = X[10] = W[10]; ROUND_00_15(10,g,h,a,b,c,d,e,f);
T1 = X[11] = W[11]; ROUND_00_15(11,f,g,h,a,b,c,d,e);
T1 = X[12] = W[12]; ROUND_00_15(12,e,f,g,h,a,b,c,d);
T1 = X[13] = W[13]; ROUND_00_15(13,d,e,f,g,h,a,b,c);
T1 = X[14] = W[14]; ROUND_00_15(14,c,d,e,f,g,h,a,b);
T1 = X[15] = W[15]; ROUND_00_15(15,b,c,d,e,f,g,h,a);
#else
T1 = X[0] = PULL64(W[0]); ROUND_00_15(0,a,b,c,d,e,f,g,h);
T1 = X[1] = PULL64(W[1]); ROUND_00_15(1,h,a,b,c,d,e,f,g);
T1 = X[2] = PULL64(W[2]); ROUND_00_15(2,g,h,a,b,c,d,e,f);
T1 = X[3] = PULL64(W[3]); ROUND_00_15(3,f,g,h,a,b,c,d,e);
T1 = X[4] = PULL64(W[4]); ROUND_00_15(4,e,f,g,h,a,b,c,d);
T1 = X[5] = PULL64(W[5]); ROUND_00_15(5,d,e,f,g,h,a,b,c);
T1 = X[6] = PULL64(W[6]); ROUND_00_15(6,c,d,e,f,g,h,a,b);
T1 = X[7] = PULL64(W[7]); ROUND_00_15(7,b,c,d,e,f,g,h,a);
T1 = X[8] = PULL64(W[8]); ROUND_00_15(8,a,b,c,d,e,f,g,h);
T1 = X[9] = PULL64(W[9]); ROUND_00_15(9,h,a,b,c,d,e,f,g);
T1 = X[10] = PULL64(W[10]); ROUND_00_15(10,g,h,a,b,c,d,e,f);
T1 = X[11] = PULL64(W[11]); ROUND_00_15(11,f,g,h,a,b,c,d,e);
T1 = X[12] = PULL64(W[12]); ROUND_00_15(12,e,f,g,h,a,b,c,d);
T1 = X[13] = PULL64(W[13]); ROUND_00_15(13,d,e,f,g,h,a,b,c);
T1 = X[14] = PULL64(W[14]); ROUND_00_15(14,c,d,e,f,g,h,a,b);
T1 = X[15] = PULL64(W[15]); ROUND_00_15(15,b,c,d,e,f,g,h,a);
#endif
for (i=16;i<80;i+=16)
{
ROUND_16_80(i, 0,a,b,c,d,e,f,g,h,X);
ROUND_16_80(i, 1,h,a,b,c,d,e,f,g,X);
ROUND_16_80(i, 2,g,h,a,b,c,d,e,f,X);
ROUND_16_80(i, 3,f,g,h,a,b,c,d,e,X);
ROUND_16_80(i, 4,e,f,g,h,a,b,c,d,X);
ROUND_16_80(i, 5,d,e,f,g,h,a,b,c,X);
ROUND_16_80(i, 6,c,d,e,f,g,h,a,b,X);
ROUND_16_80(i, 7,b,c,d,e,f,g,h,a,X);
ROUND_16_80(i, 8,a,b,c,d,e,f,g,h,X);
ROUND_16_80(i, 9,h,a,b,c,d,e,f,g,X);
ROUND_16_80(i,10,g,h,a,b,c,d,e,f,X);
ROUND_16_80(i,11,f,g,h,a,b,c,d,e,X);
ROUND_16_80(i,12,e,f,g,h,a,b,c,d,X);
ROUND_16_80(i,13,d,e,f,g,h,a,b,c,X);
ROUND_16_80(i,14,c,d,e,f,g,h,a,b,X);
ROUND_16_80(i,15,b,c,d,e,f,g,h,a,X);
}
ctx->h[0] += a; ctx->h[1] += b; ctx->h[2] += c; ctx->h[3] += d;
ctx->h[4] += e; ctx->h[5] += f; ctx->h[6] += g; ctx->h[7] += h;
}

View File

@ -0,0 +1,92 @@
/* crypto/sha/sha512.h */
/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
* All rights reserved.
*
* This package is an SSL implementation written
* by Eric Young (eay@cryptsoft.com).
* The implementation was written so as to conform with Netscapes SSL.
*
* This library is free for commercial and non-commercial use as long as
* the following conditions are aheared to. The following conditions
* apply to all code found in this distribution, be it the RC4, RSA,
* lhash, DES, etc., code; not just the SSL code. The SSL documentation
* included with this distribution is covered by the same copyright terms
* except that the holder is Tim Hudson (tjh@cryptsoft.com).
*
* Copyright remains Eric Young's, and as such any Copyright notices in
* the code are not to be removed.
* If this package is used in a product, Eric Young should be given attribution
* as the author of the parts of the library used.
* This can be in the form of a textual message at program startup or
* in documentation (online or textual) provided with the package.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* "This product includes cryptographic software written by
* Eric Young (eay@cryptsoft.com)"
* The word 'cryptographic' can be left out if the rouines from the library
* being used are not cryptographic related :-).
* 4. If you include any Windows specific code (or a derivative thereof) from
* the apps directory (application code) you must include an acknowledgement:
* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
*
* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* The licence and distribution terms for any publically available version or
* derivative of this code cannot be changed. i.e. this code cannot simply be
* copied and put under another distribution licence
* [including the GNU Public Licence.]
*/
#ifndef HEADER_SHA512_H
#define HEADER_SHA512_H
#include "BaseTypes.h"
#ifdef __cplusplus
extern "C" {
#endif
#define SHA512_DIGEST_LENGTH 64
#define SHA512_CBLOCK 128 /* SHA-512 treats input data as a
* contiguous array of 64 bit
* wide big-endian values. */
#define SHA_LONG64 U64
typedef struct SHA512state_st
{
SHA_LONG64 h[8];
SHA_LONG64 Nl,Nh;
union {
SHA_LONG64 d[8];
unsigned char p[SHA512_CBLOCK];
} u;
unsigned int num, md_len;
} SHA512_CTX;
void SHA512_Init(SHA512_CTX *c);
void SHA512_Update(SHA512_CTX *c, const void *data, size_t len);
void SHA512_Final(unsigned char *md, SHA512_CTX *c);
#ifdef __cplusplus
}
#endif
#endif

1
source/extern/opus vendored Submodule

@ -0,0 +1 @@
Subproject commit 9fc8fc4cf432640f284113ba502ee027268b0d9f

View File

@ -56,6 +56,9 @@ SET (star_frontend_HEADERS
StarStatusPane.hpp StarStatusPane.hpp
StarTeleportDialog.hpp StarTeleportDialog.hpp
StarWireInterface.hpp StarWireInterface.hpp
StarVoice.hpp
StarVoiceLuaBindings.hpp
StarVoiceSettingsMenu.hpp
) )
SET (star_frontend_SOURCES SET (star_frontend_SOURCES
@ -104,6 +107,9 @@ SET (star_frontend_SOURCES
StarStatusPane.cpp StarStatusPane.cpp
StarTeleportDialog.cpp StarTeleportDialog.cpp
StarWireInterface.cpp StarWireInterface.cpp
StarVoice.cpp
StarVoiceLuaBindings.cpp
StarVoiceSettingsMenu.cpp
) )
ADD_LIBRARY (star_frontend OBJECT ${star_frontend_SOURCES} ${star_frontend_HEADERS}) ADD_LIBRARY (star_frontend OBJECT ${star_frontend_SOURCES} ${star_frontend_HEADERS})

View File

@ -10,6 +10,7 @@
#include "StarAiInterface.hpp" #include "StarAiInterface.hpp"
#include "StarQuestInterface.hpp" #include "StarQuestInterface.hpp"
#include "StarStatistics.hpp" #include "StarStatistics.hpp"
#include "StarInterfaceLuaBindings.hpp"
namespace Star { namespace Star {
@ -76,11 +77,10 @@ StringList ClientCommandProcessor::handleCommand(String const& commandLine) {
String allArguments = commandLine.substr(1); String allArguments = commandLine.substr(1);
String command = allArguments.extract(); String command = allArguments.extract();
auto arguments = m_parser.tokenizeToStringList(allArguments);
StringList result; StringList result;
if (auto builtinCommand = m_builtinCommands.maybe(command)) { if (auto builtinCommand = m_builtinCommands.maybe(command)) {
result.append((*builtinCommand)(arguments)); result.append((*builtinCommand)(allArguments));
} else if (auto macroCommand = m_macroCommands.maybe(command)) { } else if (auto macroCommand = m_macroCommands.maybe(command)) {
for (auto const& c : *macroCommand) { for (auto const& c : *macroCommand) {
if (c.beginsWith("/")) if (c.beginsWith("/"))
@ -89,6 +89,10 @@ StringList ClientCommandProcessor::handleCommand(String const& commandLine) {
result.append(c); result.append(c);
} }
} else { } else {
auto player = m_universeClient->mainPlayer();
if (auto messageResult = player->receiveMessage(connectionForEntity(player->entityId()), strf("/{}", command), { allArguments }))
result.append(messageResult->isType(Json::Type::String) ? *messageResult->stringPtr() : messageResult->repr(1, true));
else
m_universeClient->sendChat(commandLine, ChatSendMode::Broadcast); m_universeClient->sendChat(commandLine, ChatSendMode::Broadcast);
} }
return result; return result;
@ -130,7 +134,8 @@ String ClientCommandProcessor::gravity() {
return toString(m_universeClient->worldClient()->gravity(m_universeClient->mainPlayer()->position())); return toString(m_universeClient->worldClient()->gravity(m_universeClient->mainPlayer()->position()));
} }
String ClientCommandProcessor::debug(StringList const& arguments) { String ClientCommandProcessor::debug(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -168,7 +173,8 @@ String ClientCommandProcessor::asyncLighting() {
? "enabled" : "disabled"); ? "enabled" : "disabled");
} }
String ClientCommandProcessor::setGravity(StringList const& arguments) { String ClientCommandProcessor::setGravity(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -198,7 +204,8 @@ String ClientCommandProcessor::monochromeLighting() {
return strf("Monochrome lighting {}", monochrome ? "enabled" : "disabled"); return strf("Monochrome lighting {}", monochrome ? "enabled" : "disabled");
} }
String ClientCommandProcessor::radioMessage(StringList const& arguments) { String ClientCommandProcessor::radioMessage(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -225,7 +232,8 @@ String ClientCommandProcessor::clearCinematics() {
return "Player cinematic records cleared!"; return "Player cinematic records cleared!";
} }
String ClientCommandProcessor::startQuest(StringList const& arguments) { String ClientCommandProcessor::startQuest(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -234,7 +242,8 @@ String ClientCommandProcessor::startQuest(StringList const& arguments) {
return "Quest started"; return "Quest started";
} }
String ClientCommandProcessor::completeQuest(StringList const& arguments) { String ClientCommandProcessor::completeQuest(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -242,7 +251,8 @@ String ClientCommandProcessor::completeQuest(StringList const& arguments) {
return strf("Quest {} complete", arguments.at(0)); return strf("Quest {} complete", arguments.at(0));
} }
String ClientCommandProcessor::failQuest(StringList const& arguments) { String ClientCommandProcessor::failQuest(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -250,7 +260,8 @@ String ClientCommandProcessor::failQuest(StringList const& arguments) {
return strf("Quest {} failed", arguments.at(0)); return strf("Quest {} failed", arguments.at(0));
} }
String ClientCommandProcessor::previewNewQuest(StringList const& arguments) { String ClientCommandProcessor::previewNewQuest(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -259,7 +270,8 @@ String ClientCommandProcessor::previewNewQuest(StringList const& arguments) {
}); });
} }
String ClientCommandProcessor::previewQuestComplete(StringList const& arguments) { String ClientCommandProcessor::previewQuestComplete(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -268,7 +280,8 @@ String ClientCommandProcessor::previewQuestComplete(StringList const& arguments)
}); });
} }
String ClientCommandProcessor::previewQuestFailed(StringList const& arguments) { String ClientCommandProcessor::previewQuestFailed(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -294,7 +307,8 @@ String ClientCommandProcessor::deathCount() {
return strf("Total deaths: {}{}", deaths, deaths == 0 ? ". Well done!" : ""); return strf("Total deaths: {}{}", deaths, deaths == 0 ? ". Well done!" : "");
} }
String ClientCommandProcessor::cinema(StringList const& arguments) { String ClientCommandProcessor::cinema(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -326,7 +340,8 @@ String ClientCommandProcessor::resetAchievements() {
return "Unable to reset achievements"; return "Unable to reset achievements";
} }
String ClientCommandProcessor::statistic(StringList const& arguments) { String ClientCommandProcessor::statistic(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -337,7 +352,8 @@ String ClientCommandProcessor::statistic(StringList const& arguments) {
return values.join("\n"); return values.join("\n");
} }
String ClientCommandProcessor::giveEssentialItem(StringList const& arguments) { String ClientCommandProcessor::giveEssentialItem(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -354,7 +370,8 @@ String ClientCommandProcessor::giveEssentialItem(StringList const& arguments) {
} }
} }
String ClientCommandProcessor::makeTechAvailable(StringList const& arguments) { String ClientCommandProcessor::makeTechAvailable(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -365,7 +382,8 @@ String ClientCommandProcessor::makeTechAvailable(StringList const& arguments) {
return strf("Added {} to player's visible techs", arguments.at(0)); return strf("Added {} to player's visible techs", arguments.at(0));
} }
String ClientCommandProcessor::enableTech(StringList const& arguments) { String ClientCommandProcessor::enableTech(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -377,7 +395,8 @@ String ClientCommandProcessor::enableTech(StringList const& arguments) {
return strf("Player tech {} enabled", arguments.at(0)); return strf("Player tech {} enabled", arguments.at(0));
} }
String ClientCommandProcessor::upgradeShip(StringList const& arguments) { String ClientCommandProcessor::upgradeShip(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";

View File

@ -29,40 +29,40 @@ private:
String reload(); String reload();
String whoami(); String whoami();
String gravity(); String gravity();
String debug(StringList const& arguments); String debug(String const& argumentsString);
String boxes(); String boxes();
String fullbright(); String fullbright();
String asyncLighting(); String asyncLighting();
String setGravity(StringList const& arguments); String setGravity(String const& argumentsString);
String resetGravity(); String resetGravity();
String fixedCamera(); String fixedCamera();
String monochromeLighting(); String monochromeLighting();
String radioMessage(StringList const& arguments); String radioMessage(String const& argumentsString);
String clearRadioMessages(); String clearRadioMessages();
String clearCinematics(); String clearCinematics();
String startQuest(StringList const& arguments); String startQuest(String const& argumentsString);
String completeQuest(StringList const& arguments); String completeQuest(String const& argumentsString);
String failQuest(StringList const& arguments); String failQuest(String const& argumentsString);
String previewNewQuest(StringList const& arguments); String previewNewQuest(String const& argumentsString);
String previewQuestComplete(StringList const& arguments); String previewQuestComplete(String const& argumentsString);
String previewQuestFailed(StringList const& arguments); String previewQuestFailed(String const& argumentsString);
String clearScannedObjects(); String clearScannedObjects();
String playTime(); String playTime();
String deathCount(); String deathCount();
String cinema(StringList const& arguments); String cinema(String const& argumentsString);
String suicide(); String suicide();
String naked(); String naked();
String resetAchievements(); String resetAchievements();
String statistic(StringList const& arguments); String statistic(String const& argumentsString);
String giveEssentialItem(StringList const& arguments); String giveEssentialItem(String const& argumentsString);
String makeTechAvailable(StringList const& arguments); String makeTechAvailable(String const& argumentsString);
String enableTech(StringList const& arguments); String enableTech(String const& argumentsString);
String upgradeShip(StringList const& arguments); String upgradeShip(String const& argumentsString);
UniverseClientPtr m_universeClient; UniverseClientPtr m_universeClient;
CinematicPtr m_cinematicOverlay; CinematicPtr m_cinematicOverlay;
MainInterfacePaneManager* m_paneManager; MainInterfacePaneManager* m_paneManager;
CaseInsensitiveStringMap<function<String(StringList const&)>> m_builtinCommands; CaseInsensitiveStringMap<function<String(String const&)>> m_builtinCommands;
StringMap<StringList> m_macroCommands; StringMap<StringList> m_macroCommands;
ShellParser m_parser; ShellParser m_parser;
LuaBaseComponent m_scriptComponent; LuaBaseComponent m_scriptComponent;

View File

@ -27,6 +27,11 @@ LuaCallbacks LuaBindings::makeInterfaceCallbacks(MainInterface* mainInterface) {
return GuiContext::singleton().interfaceScale(); return GuiContext::singleton().interfaceScale();
}); });
callbacks.registerCallback("queueMessage", [mainInterface](String const& message, Maybe<float> cooldown, Maybe<float> springState) {
mainInterface->queueMessage(message, cooldown, springState.value(0));
});
return callbacks; return callbacks;
} }

View File

@ -62,8 +62,8 @@ namespace Star {
GuiMessage::GuiMessage() : message(), cooldown(), springState() {} GuiMessage::GuiMessage() : message(), cooldown(), springState() {}
GuiMessage::GuiMessage(String const& message, float cooldown) GuiMessage::GuiMessage(String const& message, float cooldown, float spring)
: message(message), cooldown(cooldown), springState(0) {} : message(message), cooldown(cooldown), springState(spring) {}
MainInterface::MainInterface(UniverseClientPtr client, WorldPainterPtr painter, CinematicPtr cinematicOverlay) { MainInterface::MainInterface(UniverseClientPtr client, WorldPainterPtr painter, CinematicPtr cinematicOverlay) {
m_state = Running; m_state = Running;
@ -369,6 +369,9 @@ bool MainInterface::handleInputEvent(InputEvent const& event) {
player->endTrigger(); player->endTrigger();
} }
for (auto& pair : m_canvases)
pair.second->sendEvent(event);
return true; return true;
} }
@ -863,11 +866,15 @@ void MainInterface::doChat(String const& chat, bool addToHistory) {
m_chat->addHistory(chat); m_chat->addHistory(chat);
} }
void MainInterface::queueMessage(String const& message) { void MainInterface::queueMessage(String const& message, Maybe<float> cooldown, float spring) {
auto guiMessage = make_shared<GuiMessage>(message, m_config->messageTime); auto guiMessage = make_shared<GuiMessage>(message, cooldown.value(m_config->messageTime), spring);
m_messages.append(guiMessage); m_messages.append(guiMessage);
} }
void MainInterface::queueMessage(String const& message) {
queueMessage(message, m_config->messageTime, 0.0f);
}
void MainInterface::queueJoinRequest(pair<String, RpcPromiseKeeper<P2PJoinRequestReply>> request) void MainInterface::queueJoinRequest(pair<String, RpcPromiseKeeper<P2PJoinRequestReply>> request)
{ {
m_queuedJoinRequests.push_back(request); m_queuedJoinRequests.push_back(request);
@ -927,18 +934,21 @@ void MainInterface::warpTo(WarpAction const& warpAction) {
} }
CanvasWidgetPtr MainInterface::fetchCanvas(String const& canvasName, bool ignoreInterfaceScale) { CanvasWidgetPtr MainInterface::fetchCanvas(String const& canvasName, bool ignoreInterfaceScale) {
CanvasWidgetPtr canvas;
if (auto canvasPtr = m_canvases.ptr(canvasName)) if (auto canvasPtr = m_canvases.ptr(canvasName))
return *canvasPtr; canvas = *canvasPtr;
else { else {
CanvasWidgetPtr canvas = m_canvases.emplace(canvasName, make_shared<CanvasWidget>()).first->second; m_canvases.emplace(canvasName, canvas = make_shared<CanvasWidget>());
canvas->setPosition(Vec2I()); canvas->setPosition(Vec2I());
if (ignoreInterfaceScale) if (ignoreInterfaceScale)
canvas->setSize(Vec2I(m_guiContext->windowSize())); canvas->setSize(Vec2I(m_guiContext->windowSize()));
else else
canvas->setSize(Vec2I(m_guiContext->windowInterfaceSize())); canvas->setSize(Vec2I(m_guiContext->windowInterfaceSize()));
}
canvas->setIgnoreInterfaceScale(ignoreInterfaceScale); canvas->setIgnoreInterfaceScale(ignoreInterfaceScale);
return canvas; return canvas;
}
} }
PanePtr MainInterface::createEscapeDialog() { PanePtr MainInterface::createEscapeDialog() {

View File

@ -49,7 +49,7 @@ STAR_CLASS(MainInterface);
struct GuiMessage { struct GuiMessage {
GuiMessage(); GuiMessage();
GuiMessage(String const& message, float cooldown); GuiMessage(String const& message, float cooldown, float spring = 0);
String message; String message;
float cooldown; float cooldown;
@ -105,7 +105,9 @@ public:
void doChat(String const& chat, bool addToHistory); void doChat(String const& chat, bool addToHistory);
void queueMessage(String const& message, Maybe<float> cooldown, float spring);
void queueMessage(String const& message); void queueMessage(String const& message);
void queueItemPickupText(ItemPtr const& item); void queueItemPickupText(ItemPtr const& item);
void queueJoinRequest(pair<String, RpcPromiseKeeper<P2PJoinRequestReply>> request); void queueJoinRequest(pair<String, RpcPromiseKeeper<P2PJoinRequestReply>> request);

View File

@ -7,6 +7,7 @@
#include "StarAssets.hpp" #include "StarAssets.hpp"
#include "StarWorldClient.hpp" #include "StarWorldClient.hpp"
#include "StarWorldPainter.hpp" #include "StarWorldPainter.hpp"
#include "StarVoice.hpp"
namespace Star { namespace Star {
@ -80,7 +81,7 @@ void MainMixer::update(bool muteSfx, bool muteMusic) {
auto cameraPos = m_worldPainter->camera().centerWorldPosition(); auto cameraPos = m_worldPainter->camera().centerWorldPosition();
auto worldGeometry = currentWorld->geometry(); auto worldGeometry = currentWorld->geometry();
m_mixer->update([&](unsigned channel, Vec2F pos, float rangeMultiplier) { Mixer::PositionalAttenuationFunction attenuationFunction = [&](unsigned channel, Vec2F pos, float rangeMultiplier) {
Vec2F playerDiff = worldGeometry.diff(pos, playerPos); Vec2F playerDiff = worldGeometry.diff(pos, playerPos);
Vec2F cameraDiff = worldGeometry.diff(pos, cameraPos); Vec2F cameraDiff = worldGeometry.diff(pos, cameraPos);
float playerMagSq = playerDiff.magnitudeSquared(); float playerMagSq = playerDiff.magnitudeSquared();
@ -107,7 +108,12 @@ void MainMixer::update(bool muteSfx, bool muteMusic) {
float maxDistance = baseMaxDistance * rangeMultiplier * lerp((stereoIncidence + 1.0f) / 2.0f, stereoAdjustmentRange[0], stereoAdjustmentRange[1]); float maxDistance = baseMaxDistance * rangeMultiplier * lerp((stereoIncidence + 1.0f) / 2.0f, stereoAdjustmentRange[0], stereoAdjustmentRange[1]);
return pow(clamp(diffMagnitude / maxDistance, 0.0f, 1.0f), 1.0f / attenuationGamma); return pow(clamp(diffMagnitude / maxDistance, 0.0f, 1.0f), 1.0f / attenuationGamma);
}); };
if (Voice* voice = Voice::singletonPtr())
voice->update(attenuationFunction);
m_mixer->update(attenuationFunction);
} else { } else {
if (m_mixer->hasEffect("lowpass")) if (m_mixer->hasEffect("lowpass"))
@ -115,6 +121,9 @@ void MainMixer::update(bool muteSfx, bool muteMusic) {
if (m_mixer->hasEffect("echo")) if (m_mixer->hasEffect("echo"))
m_mixer->removeEffect("echo", 0); m_mixer->removeEffect("echo", 0);
if (Voice* voice = Voice::singletonPtr())
voice->update();
m_mixer->update(); m_mixer->update();
} }
} }
@ -127,8 +136,8 @@ void MainMixer::setVolume(float volume, float rampTime) {
m_mixer->setVolume(volume, rampTime); m_mixer->setVolume(volume, rampTime);
} }
void MainMixer::read(int16_t* sampleData, size_t frameCount) { void MainMixer::read(int16_t* sampleData, size_t frameCount, Mixer::ExtraMixFunction extraMixFunction) {
m_mixer->read(sampleData, frameCount); m_mixer->read(sampleData, frameCount, extraMixFunction);
} }
} }

View File

@ -22,7 +22,7 @@ public:
MixerPtr mixer() const; MixerPtr mixer() const;
void setVolume(float volume, float rampTime = 0.0f); void setVolume(float volume, float rampTime = 0.0f);
void read(int16_t* sampleData, size_t frameCount); void read(int16_t* sampleData, size_t frameCount, Mixer::ExtraMixFunction = {});
private: private:
UniverseClientPtr m_universeClient; UniverseClientPtr m_universeClient;

View File

@ -7,6 +7,7 @@
#include "StarLabelWidget.hpp" #include "StarLabelWidget.hpp"
#include "StarAssets.hpp" #include "StarAssets.hpp"
#include "StarKeybindingsMenu.hpp" #include "StarKeybindingsMenu.hpp"
#include "StarVoiceSettingsMenu.hpp"
#include "StarBindingsMenu.hpp" #include "StarBindingsMenu.hpp"
#include "StarGraphicsMenu.hpp" #include "StarGraphicsMenu.hpp"
@ -49,6 +50,12 @@ OptionsMenu::OptionsMenu(PaneManager* manager)
reader.registerCallback("showKeybindings", [=](Widget*) { reader.registerCallback("showKeybindings", [=](Widget*) {
displayControls(); displayControls();
}); });
reader.registerCallback("showVoiceSettings", [=](Widget*) {
displayVoiceSettings();
});
reader.registerCallback("showVoicePlayers", [=](Widget*) {
});
reader.registerCallback("showModBindings", [=](Widget*) { reader.registerCallback("showModBindings", [=](Widget*) {
displayModBindings(); displayModBindings();
}); });
@ -74,6 +81,7 @@ OptionsMenu::OptionsMenu(PaneManager* manager)
m_sfxSlider->setRange(m_sfxRange, assets->json("/interface/optionsmenu/optionsmenu.config:sfxDelta").toInt()); m_sfxSlider->setRange(m_sfxRange, assets->json("/interface/optionsmenu/optionsmenu.config:sfxDelta").toInt());
m_musicSlider->setRange(m_musicRange, assets->json("/interface/optionsmenu/optionsmenu.config:musicDelta").toInt()); m_musicSlider->setRange(m_musicRange, assets->json("/interface/optionsmenu/optionsmenu.config:musicDelta").toInt());
m_voiceSettingsMenu = make_shared<VoiceSettingsMenu>(assets->json(config.getString("voiceSettingsPanePath", "/interface/opensb/voicechat/voicechat.config")));
m_modBindingsMenu = make_shared<BindingsMenu>(assets->json(config.getString("bindingsPanePath", "/interface/opensb/bindings/bindings.config"))); m_modBindingsMenu = make_shared<BindingsMenu>(assets->json(config.getString("bindingsPanePath", "/interface/opensb/bindings/bindings.config")));
m_keybindingsMenu = make_shared<KeybindingsMenu>(); m_keybindingsMenu = make_shared<KeybindingsMenu>();
m_graphicsMenu = make_shared<GraphicsMenu>(); m_graphicsMenu = make_shared<GraphicsMenu>();
@ -169,6 +177,10 @@ void OptionsMenu::displayControls() {
m_paneManager->displayPane(PaneLayer::ModalWindow, m_keybindingsMenu); m_paneManager->displayPane(PaneLayer::ModalWindow, m_keybindingsMenu);
} }
void OptionsMenu::displayVoiceSettings() {
m_paneManager->displayPane(PaneLayer::ModalWindow, m_voiceSettingsMenu);
}
void OptionsMenu::displayModBindings() { void OptionsMenu::displayModBindings() {
m_paneManager->displayPane(PaneLayer::ModalWindow, m_modBindingsMenu); m_paneManager->displayPane(PaneLayer::ModalWindow, m_modBindingsMenu);
} }

View File

@ -10,6 +10,7 @@ namespace Star {
STAR_CLASS(SliderBarWidget); STAR_CLASS(SliderBarWidget);
STAR_CLASS(ButtonWidget); STAR_CLASS(ButtonWidget);
STAR_CLASS(LabelWidget); STAR_CLASS(LabelWidget);
STAR_CLASS(VoiceSettingsMenu);
STAR_CLASS(KeybindingsMenu); STAR_CLASS(KeybindingsMenu);
STAR_CLASS(GraphicsMenu); STAR_CLASS(GraphicsMenu);
STAR_CLASS(BindingsMenu); STAR_CLASS(BindingsMenu);
@ -38,6 +39,7 @@ private:
void syncGuiToConf(); void syncGuiToConf();
void displayControls(); void displayControls();
void displayVoiceSettings();
void displayModBindings(); void displayModBindings();
void displayGraphics(); void displayGraphics();
@ -59,6 +61,7 @@ private:
JsonObject m_origConfig; JsonObject m_origConfig;
JsonObject m_localChanges; JsonObject m_localChanges;
VoiceSettingsMenuPtr m_voiceSettingsMenu;
BindingsMenuPtr m_modBindingsMenu; BindingsMenuPtr m_modBindingsMenu;
KeybindingsMenuPtr m_keybindingsMenu; KeybindingsMenuPtr m_keybindingsMenu;
GraphicsMenuPtr m_graphicsMenu; GraphicsMenuPtr m_graphicsMenu;

View File

@ -0,0 +1,716 @@
#include "StarVoice.hpp"
#include "StarFormat.hpp"
#include "StarJsonExtra.hpp"
#include "StarApplicationController.hpp"
#include "StarTime.hpp"
#include "StarRoot.hpp"
#include "StarLogging.hpp"
#include "StarInterpolation.hpp"
#include "opus/include/opus.h"
#include "SDL.h"
constexpr int VOICE_SAMPLE_RATE = 48000;
constexpr int VOICE_FRAME_SIZE = 960;
constexpr int VOICE_MAX_FRAME_SIZE = 6 * VOICE_FRAME_SIZE;
constexpr int VOICE_MAX_PACKET_SIZE = 3 * 1276;
constexpr uint16_t VOICE_VERSION = 1;
namespace Star {
EnumMap<VoiceInputMode> const VoiceInputModeNames{
{VoiceInputMode::VoiceActivity, "VoiceActivity"},
{VoiceInputMode::PushToTalk, "PushToTalk"}
};
EnumMap<VoiceChannelMode> const VoiceChannelModeNames{
{VoiceChannelMode::Mono, "Mono"},
{VoiceChannelMode::Stereo, "Stereo"}
};
inline float getAudioChunkLoudness(int16_t* data, size_t samples, float volume) {
if (!samples)
return 0.f;
double rms = 0.;
for (size_t i = 0; i != samples; ++i) {
float sample = ((float)data[i] / 32767.f) * volume;
rms += (double)(sample * sample);
}
float fRms = sqrtf((float)(rms / samples));
if (fRms > 0)
return std::clamp<float>(20.f * log10f(fRms), -127.f, 0.f);
else
return -127.f;
}
float getAudioLoudness(int16_t* data, size_t samples, float volume = 1.0f) {
constexpr size_t CHUNK_SIZE = 50;
float highest = -127.f;
for (size_t i = 0; i < samples; i += CHUNK_SIZE) {
float level = getAudioChunkLoudness(data + i, std::min<size_t>(i + CHUNK_SIZE, samples) - i, volume);
if (level > highest)
highest = level;
}
return highest;
}
struct VoiceAudioStream {
// TODO: This should really be a ring buffer instead.
std::queue<int16_t> samples;
SDL_AudioStream* sdlAudioStreamMono;
SDL_AudioStream* sdlAudioStreamStereo;
Mutex mutex;
VoiceAudioStream()
: sdlAudioStreamMono (SDL_NewAudioStream(AUDIO_S16, 1, 48000, AUDIO_S16SYS, 1, 44100))
, sdlAudioStreamStereo(SDL_NewAudioStream(AUDIO_S16, 2, 48000, AUDIO_S16SYS, 2, 44100)) {};
~VoiceAudioStream() {
SDL_FreeAudioStream(sdlAudioStreamMono);
SDL_FreeAudioStream(sdlAudioStreamStereo);
}
inline int16_t take() {
int16_t sample = 0;
if (!samples.empty()) {
sample = samples.front();
samples.pop();
}
return sample;
}
size_t resample(int16_t* in, size_t inSamples, std::vector<int16_t>& out, bool mono) {
SDL_AudioStream* stream = mono ? sdlAudioStreamMono : sdlAudioStreamStereo;
SDL_AudioStreamPut(stream, in, inSamples * sizeof(int16_t));
if (int available = SDL_AudioStreamAvailable(stream)) {
out.resize(available / 2);
SDL_AudioStreamGet(stream, out.data(), available);
return available;
}
return 0;
}
};
Voice::Speaker::Speaker(SpeakerId id)
: decoderMono (createDecoder(1), opus_decoder_destroy)
, decoderStereo(createDecoder(2), opus_decoder_destroy) {
speakerId = id;
audioStream = make_shared<VoiceAudioStream>();
}
Json Voice::Speaker::toJson() const {
return JsonObject{
{"speakerId", speakerId},
{"entityId", entityId },
{"name", name },
{"playing", (bool)playing},
{"muted", (bool)muted },
{"decibels", (float)decibelLevel},
{"smoothDecibels", (float)smoothDb },
{"position", jsonFromVec2F(position)}
};
}
Voice* Voice::s_singleton;
Voice* Voice::singletonPtr() {
return s_singleton;
}
Voice& Voice::singleton() {
if (!s_singleton)
throw VoiceException("Voice::singleton() called with no Voice instance available");
else
return *s_singleton;
}
Voice::Voice(ApplicationControllerPtr appController) : m_encoder(nullptr, opus_encoder_destroy) {
if (s_singleton)
throw VoiceException("Singleton Voice has been constructed twice");
m_clientSpeaker = make_shared<Speaker>(m_speakerId);
m_inputMode = VoiceInputMode::PushToTalk;
m_channelMode = VoiceChannelMode::Mono;
m_applicationController = appController;
m_stopThread = false;
m_thread = Thread::invoke("Voice::thread", mem_fn(&Voice::thread), this);
s_singleton = this;
}
Voice::~Voice() {
m_stopThread = true;
{
MutexLocker locker(m_threadMutex);
m_threadCond.broadcast();
}
m_thread.finish();
if (m_nextSaveTime)
save();
closeDevice();
s_singleton = nullptr;
}
void Voice::init() {
resetEncoder();
resetDevice();
}
template <typename T>
inline bool change(T& value, T newValue, bool& out) {
bool changed = value != newValue;
out |= changed;
value = move(newValue);
return changed;
}
void Voice::loadJson(Json const& config, bool skipSave) {
// Not all keys are required
bool changed = false;
{
bool enabled = shouldEnableInput();
m_enabled = config.getBool("enabled", m_enabled);
m_inputEnabled = config.getBool("inputEnabled", m_inputEnabled);
if (shouldEnableInput() != enabled) {
changed = true;
resetDevice();
}
}
if (config.contains("deviceName") // Make sure null-type key exists
&& change(m_deviceName, config.optString("deviceName"), changed))
resetDevice();
m_threshold = config.getFloat("threshold", m_threshold);
m_inputVolume = config.getFloat("inputVolume", m_inputVolume);
m_outputVolume = config.getFloat("outputVolume", m_outputVolume);
if (change(m_loopback, config.getBool("loopback", m_loopback), changed))
m_clientSpeaker->playing = false;
if (auto inputMode = config.optString("inputMode")) {
if (change(m_inputMode, VoiceInputModeNames.getLeft(*inputMode), changed))
m_lastInputTime = 0;
}
if (auto channelMode = config.optString("channelMode")) {
if (change(m_channelMode, VoiceChannelModeNames.getLeft(*channelMode), changed)) {
closeDevice();
resetEncoder();
resetDevice();
}
}
if (changed && !skipSave)
scheduleSave();
}
Json Voice::saveJson() const {
return JsonObject{
{"enabled", m_enabled},
{"inputEnabled", m_inputEnabled},
{"inputDevice", m_deviceName ? *m_deviceName : Json()},
{"threshold", m_threshold},
{"inputVolume", m_inputVolume},
{"outputVolume", m_outputVolume},
{"inputMode", VoiceInputModeNames.getRight(m_inputMode)},
{"channelMode", VoiceChannelModeNames.getRight(m_channelMode)},
{"loopback", m_loopback},
{"version", 1}
};
}
void Voice::save() const {
if (Root* root = Root::singletonPtr()) {
if (auto config = root->configuration())
config->set("voice", saveJson());
}
}
void Voice::scheduleSave() {
if (!m_nextSaveTime)
m_nextSaveTime = Time::monotonicMilliseconds() + 2000;
}
Voice::SpeakerPtr Voice::setLocalSpeaker(SpeakerId speakerId) {
if (m_speakers.contains(m_speakerId))
m_speakers.remove(m_speakerId);
m_clientSpeaker->speakerId = m_speakerId = speakerId;
return m_speakers.insert(m_speakerId, m_clientSpeaker).first->second;
}
Voice::SpeakerPtr Voice::localSpeaker() {
return m_clientSpeaker;
}
Voice::SpeakerPtr Voice::speaker(SpeakerId speakerId) {
if (m_speakerId == speakerId)
return m_clientSpeaker;
else {
if (SpeakerPtr const* ptr = m_speakers.ptr(speakerId))
return *ptr;
else
return m_speakers.emplace(speakerId, make_shared<Speaker>(speakerId)).first->second;
}
}
HashMap<Voice::SpeakerId, Voice::SpeakerPtr>& Voice::speakers() {
return m_speakers;
}
List<Voice::SpeakerPtr> Voice::sortedSpeakers(bool onlyPlaying) {
List<SpeakerPtr> result;
auto sorter = [](SpeakerPtr const& a, SpeakerPtr const& b) -> bool {
if (a->lastPlayTime != b->lastPlayTime)
return a->lastPlayTime < b->lastPlayTime;
else
return a->speakerId < b->speakerId;
};
for (auto& p : m_speakers) {
if (!onlyPlaying || p.second->playing)
result.insertSorted(p.second, sorter);
}
return result;
}
void Voice::clearSpeakers() {
auto it = m_speakers.begin();
while (it != m_speakers.end()) {
if (it->second == m_clientSpeaker)
it = ++it;
else
it = m_speakers.erase(it);
}
}
void Voice::readAudioData(uint8_t* stream, int len) {
auto now = Time::monotonicMilliseconds();
bool active = m_encoder && m_encodedChunksLength < 2048
&& (m_inputMode == VoiceInputMode::VoiceActivity || now < m_lastInputTime);
size_t sampleCount = len / 2;
if (active) {
float decibels = getAudioLoudness((int16_t*)stream, sampleCount);
if (m_inputMode == VoiceInputMode::VoiceActivity) {
if (decibels > m_threshold)
m_lastThresholdTime = now;
active = now - m_lastThresholdTime < 50;
}
}
m_clientSpeaker->decibelLevel = getAudioLoudness((int16_t*)stream, sampleCount, m_inputVolume);
if (!m_loopback) {
if (active && !m_clientSpeaker->playing)
m_clientSpeaker->lastPlayTime = now;
m_clientSpeaker->playing = active;
}
MutexLocker captureLock(m_captureMutex);
if (active) {
m_capturedChunksFrames += sampleCount / m_deviceChannels;
auto data = (opus_int16*)malloc(len);
memcpy(data, stream, len);
m_capturedChunks.emplace(data, sampleCount); // takes ownership
m_threadCond.signal();
}
else { // Clear out any residual data so they don't manifest at the start of the next encode, whenever that is
while (!m_capturedChunks.empty())
m_capturedChunks.pop();
m_capturedChunksFrames = 0;
}
}
void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) {
size_t samples = frameCount * channels;
static std::vector<int16_t> finalBuffer, speakerBuffer;
static std::vector<int32_t> sharedBuffer; //int32 to reduce clipping
speakerBuffer.resize(samples);
sharedBuffer.resize(samples);
bool mix = false;
{
MutexLocker lock(m_activeSpeakersMutex);
auto it = m_activeSpeakers.begin();
while (it != m_activeSpeakers.end()) {
SpeakerPtr const& speaker = *it;
VoiceAudioStream* audio = speaker->audioStream.get();
MutexLocker audioLock(audio->mutex);
if (speaker->playing && !audio->samples.empty()) {
for (size_t i = 0; i != samples; ++i)
speakerBuffer[i] = audio->take();
if (speaker != m_clientSpeaker)
speaker->decibelLevel = getAudioLoudness(speakerBuffer.data(), samples);
if (!speaker->muted) {
mix = true;
float volume = speaker->volume;
Array2F levels = speaker->channelVolumes;
for (size_t i = 0; i != samples; ++i)
sharedBuffer[i] += (int32_t)(speakerBuffer[i]) * levels[i % 2] * volume;
//Blends the weaker channel into the stronger one,
/* unused, is a bit too strong on stereo music.
float maxLevel = max(levels[0], levels[1]);
float leftToRight = maxLevel != 0.0f ? 1.0f - (levels[0] / maxLevel) : 0.0f;
float rightToLeft = maxLevel != 0.0f ? 1.0f - (levels[1] / maxLevel) : 0.0f;
int16_t* speakerData = speakerBuffer.data();
int32_t* sharedData = sharedBuffer.data();
for (size_t i = 0; i != frameCount; ++i) {
auto leftSample = (float)*speakerData++;
auto rightSample = (float)*speakerData++;
if (rightToLeft != 0.0f)
leftSample = ( leftSample + rightSample * rightToLeft) / (1.0f + rightToLeft);
if (leftToRight != 0.0f)
rightSample = (rightSample + leftSample * leftToRight) / (1.0f + leftToRight);
*sharedData++ += (int32_t)leftSample * levels[0];
*sharedData++ += (int32_t)rightSample * levels[1];
}
//*/
}
++it;
}
else {
speaker->playing = false;
if (speaker != m_clientSpeaker)
speaker->decibelLevel = -96.0f;
it = m_activeSpeakers.erase(it);
}
}
}
if (mix) {
finalBuffer.resize(sharedBuffer.size(), 0);
float vol = m_outputVolume;
for (size_t i = 0; i != sharedBuffer.size(); ++i)
finalBuffer[i] = (int16_t)clamp<int>(sharedBuffer[i] * vol, INT16_MIN, INT16_MAX);
SDL_MixAudioFormat((Uint8*)buffer, (Uint8*)finalBuffer.data(), AUDIO_S16, finalBuffer.size() * sizeof(int16_t), SDL_MIX_MAXVOLUME);
memset(sharedBuffer.data(), 0, sharedBuffer.size() * sizeof(int32_t));
}
}
void Voice::update(PositionalAttenuationFunction positionalAttenuationFunction) {
for (auto& entry : m_speakers) {
if (SpeakerPtr& speaker = entry.second) {
if (positionalAttenuationFunction) {
speaker->channelVolumes = {
1.0f - positionalAttenuationFunction(0, speaker->position, 1.0f),
1.0f - positionalAttenuationFunction(1, speaker->position, 1.0f)
};
}
else
speaker->channelVolumes = Vec2F::filled(1.0f);
auto& dbHistory = speaker->dbHistory;
memcpy(&dbHistory[1], &dbHistory[0], (dbHistory.size() - 1) * sizeof(float));
dbHistory[0] = speaker->decibelLevel;
float smoothDb = 0.0f;
for (float dB : dbHistory)
smoothDb += dB;
speaker->smoothDb = smoothDb / dbHistory.size();
}
}
if (m_nextSaveTime && Time::monotonicMilliseconds() > m_nextSaveTime) {
m_nextSaveTime = 0;
save();
}
}
void Voice::setDeviceName(Maybe<String> deviceName) {
if (m_deviceName == deviceName)
return;
m_deviceName = deviceName;
if (m_deviceOpen)
openDevice();
}
StringList Voice::availableDevices() {
int devices = SDL_GetNumAudioDevices(1);
StringList deviceList;
if (devices > 0) {
deviceList.reserve(devices);
for (size_t i = 0; i != devices; ++i)
deviceList.emplace_back(SDL_GetAudioDeviceName(i, 1));
}
deviceList.sort();
return deviceList;
}
int Voice::send(DataStreamBuffer& out, size_t budget) {
out.setByteOrder(ByteOrder::LittleEndian);
out.write<uint16_t>(VOICE_VERSION);
MutexLocker encodeLock(m_encodeMutex);
if (m_encodedChunks.empty())
return 0;
std::vector<ByteArray> encodedChunks = move(m_encodedChunks);
size_t encodedChunksLength = m_encodedChunksLength;
m_encodedChunksLength = 0;
encodeLock.unlock();
for (auto& chunk : encodedChunks) {
out.write<uint32_t>(chunk.size());
out.writeBytes(chunk);
if (budget && (budget -= min<size_t>(budget, chunk.size())) == 0)
break;
}
m_lastSentTime = Time::monotonicMilliseconds();
if (m_loopback)
receive(m_clientSpeaker, { out.ptr(), out.size() });
return 1;
}
bool Voice::receive(SpeakerPtr speaker, std::string_view view) {
if (!m_enabled || !speaker || view.empty())
return false;
try {
DataStreamExternalBuffer reader(view.data(), view.size());
reader.setByteOrder(ByteOrder::LittleEndian);
if (reader.read<uint16_t>() > VOICE_VERSION)
return false;
uint32_t opusLength = 0;
while (!reader.atEnd()) {
reader >> opusLength;
if (reader.pos() + opusLength > reader.size())
throw VoiceException("Opus packet length goes past end of buffer"s, false);
auto opusData = (unsigned char*)reader.ptr() + reader.pos();
reader.seek(opusLength, IOSeek::Relative);
int channels = opus_packet_get_nb_channels(opusData);
if (channels == OPUS_INVALID_PACKET)
continue;
bool mono = channels == 1;
OpusDecoder* decoder = mono ? speaker->decoderMono.get() : speaker->decoderStereo.get();
int samples = opus_decoder_get_nb_samples(decoder, opusData, opusLength);
if (samples < 0)
throw VoiceException(strf("Decoder error: {}", opus_strerror(samples)), false);
m_decodeBuffer.resize(samples * (size_t)channels);
int decodedSamples = opus_decode(decoder, opusData, opusLength, m_decodeBuffer.data(), m_decodeBuffer.size() * sizeof(int16_t), 0);
if (decodedSamples <= 0) {
if (decodedSamples < 0)
throw VoiceException(strf("Decoder error: {}", opus_strerror(samples)), false);
return true;
}
//Logger::info("Voice: decoded Opus chunk {} bytes -> {} samples", opusLength, decodedSamples * channels);
speaker->audioStream->resample(m_decodeBuffer.data(), (size_t)decodedSamples * channels, m_resampleBuffer, mono);
{
MutexLocker lock(speaker->audioStream->mutex);
auto& samples = speaker->audioStream->samples;
auto now = Time::monotonicMilliseconds();
if (now - speaker->lastReceiveTime < 1000) {
auto limit = (size_t)speaker->minimumPlaySamples + 22050;
if (samples.size() > limit) { // skip ahead if we're getting too far
for (size_t i = samples.size(); i >= limit; --i)
samples.pop();
}
}
else
samples = std::queue<int16_t>();
speaker->lastReceiveTime = now;
if (mono) {
for (int16_t sample : m_resampleBuffer) {
samples.push(sample);
samples.push(sample);
}
}
else {
for (int16_t sample : m_resampleBuffer)
samples.push(sample);
}
}
playSpeaker(speaker, channels);
}
return true;
}
catch (StarException const& e) {
Logger::error("Voice: Error receiving voice data for speaker #{} ('{}'): {}", speaker->speakerId, speaker->name, e.what());
return false;
}
}
void Voice::setInput(bool input) {
m_lastInputTime = (m_deviceOpen && input) ? Time::monotonicMilliseconds() + 1000 : 0;
}
OpusDecoder* Voice::createDecoder(int channels) {
int error;
OpusDecoder* decoder = opus_decoder_create(VOICE_SAMPLE_RATE, channels, &error);
if (error != OPUS_OK)
throw VoiceException::format("Could not create decoder: {}", opus_strerror(error));
else
return decoder;
}
OpusEncoder* Voice::createEncoder(int channels) {
int error;
OpusEncoder* encoder = opus_encoder_create(VOICE_SAMPLE_RATE, channels, OPUS_APPLICATION_AUDIO, &error);
if (error != OPUS_OK)
throw VoiceException::format("Could not create encoder: {}", opus_strerror(error));
else
return encoder;
}
void Voice::resetEncoder() {
int channels = encoderChannels();
MutexLocker locker(m_threadMutex);
m_encoder.reset(createEncoder(channels));
opus_encoder_ctl(m_encoder.get(), OPUS_SET_BITRATE(channels == 2 ? 50000 : 24000));
}
void Voice::resetDevice() {
if (shouldEnableInput())
openDevice();
else
closeDevice();
}
void Voice::openDevice() {
closeDevice();
m_applicationController->openAudioInputDevice(
m_deviceName ? m_deviceName->utf8Ptr() : nullptr,
VOICE_SAMPLE_RATE,
m_deviceChannels = encoderChannels(),
this,
[](void* userdata, uint8_t* stream, int len) {
((Voice*)(userdata))->readAudioData(stream, len);
}
);
m_deviceOpen = true;
}
void Voice::closeDevice() {
if (!m_deviceOpen)
return;
m_applicationController->closeAudioInputDevice();
m_clientSpeaker->playing = false;
m_clientSpeaker->decibelLevel = -96.0f;
m_deviceOpen = false;
}
bool Voice::playSpeaker(SpeakerPtr const& speaker, int channels) {
if (speaker->playing || speaker->audioStream->samples.size() < speaker->minimumPlaySamples)
return false;
if (!speaker->playing) {
speaker->lastPlayTime = Time::monotonicMilliseconds();
speaker->playing = true;
MutexLocker lock(m_activeSpeakersMutex);
m_activeSpeakers.insert(speaker);
}
return true;
}
void Voice::thread() {
while (true) {
MutexLocker locker(m_threadMutex);
m_threadCond.wait(m_threadMutex);
if (m_stopThread)
return;
{
MutexLocker locker(m_captureMutex);
ByteArray encoded(VOICE_MAX_PACKET_SIZE, 0);
size_t frameSamples = VOICE_FRAME_SIZE * (size_t)m_deviceChannels;
while (m_capturedChunksFrames >= VOICE_FRAME_SIZE) {
std::vector<opus_int16> samples;
samples.reserve(frameSamples);
size_t samplesLeft = frameSamples;
while (samplesLeft && !m_capturedChunks.empty()) {
auto& front = m_capturedChunks.front();
if (front.exhausted())
m_capturedChunks.pop();
else
samplesLeft -= front.takeSamples(samples, samplesLeft);
}
m_capturedChunksFrames -= VOICE_FRAME_SIZE;
if (m_inputVolume != 1.0f) {
for (size_t i = 0; i != samples.size(); ++i)
samples[i] *= m_inputVolume;
}
if (int encodedSize = opus_encode(m_encoder.get(), samples.data(), VOICE_FRAME_SIZE, (unsigned char*)encoded.ptr(), encoded.size())) {
if (encodedSize == 1)
continue;
encoded.resize(encodedSize);
{
MutexLocker lock(m_encodeMutex);
m_encodedChunks.emplace_back(move(encoded));
m_encodedChunksLength += encodedSize;
encoded = ByteArray(VOICE_MAX_PACKET_SIZE, 0);
}
//Logger::info("Voice: encoded Opus chunk {} samples -> {} bytes", frameSamples, encodedSize);
}
else if (encodedSize < 0)
Logger::error("Voice: Opus encode error {}", opus_strerror(encodedSize));
}
}
continue;
locker.unlock();
Thread::yield();
}
return;
}
}

View File

@ -0,0 +1,226 @@
#ifndef STAR_VOICE_HPP
#define STAR_VOICE_HPP
#include "StarJson.hpp"
#include "StarBiMap.hpp"
#include "StarException.hpp"
#include "StarGameTypes.hpp"
#include "StarMaybe.hpp"
#include "StarThread.hpp"
#include "StarDataStreamDevices.hpp"
#include "StarApplicationController.hpp"
#include <queue>
struct OpusDecoder;
typedef std::unique_ptr<OpusDecoder, void(*)(OpusDecoder*)> OpusDecoderPtr;
struct OpusEncoder;
typedef std::unique_ptr<OpusEncoder, void(*)(OpusEncoder*)> OpusEncoderPtr;
namespace Star {
String const VoiceBroadcastPrefix = "Voice\0"s;
STAR_EXCEPTION(VoiceException, StarException);
enum class VoiceInputMode : uint8_t { VoiceActivity, PushToTalk };
extern EnumMap<VoiceInputMode> const VoiceInputModeNames;
enum class VoiceChannelMode: uint8_t { Mono = 1, Stereo = 2 };
extern EnumMap<VoiceChannelMode> const VoiceChannelModeNames;
STAR_CLASS(Voice);
STAR_CLASS(VoiceAudioStream);
STAR_CLASS(ApplicationController);
struct VoiceAudioChunk {
std::unique_ptr<int16_t[]> data;
size_t remaining;
size_t offset = 0;
VoiceAudioChunk(int16_t* ptr, size_t size) {
data.reset(ptr);
remaining = size;
offset = 0;
}
inline size_t takeSamples(std::vector<int16_t>& out, size_t count) {
size_t toRead = min<size_t>(count, remaining);
int16_t* start = data.get() + offset;
out.insert(out.end(), start, start + toRead);
offset += toRead;
remaining -= toRead;
return toRead;
}
//this one's unsafe
inline int16_t takeSample() {
--remaining;
return *(data.get() + offset++);
}
inline bool exhausted() { return remaining == 0; }
};
class Voice {
public:
// Individual speakers are represented by their connection ID.
typedef ConnectionId SpeakerId;
class Speaker {
public:
SpeakerId speakerId = 0;
EntityId entityId = 0;
Vec2F position = Vec2F();
String name = "Unnamed";
OpusDecoderPtr decoderMono;
OpusDecoderPtr decoderStereo;
VoiceAudioStreamPtr audioStream;
Mutex mutex;
int64_t lastReceiveTime = 0;
int64_t lastPlayTime = 0;
float smoothDb = -96.0f;
Array<float, 10> dbHistory = Array<float, 10>::filled(0);
atomic<bool> muted = false;
atomic<bool> playing = 0;
atomic<float> decibelLevel = -96.0f;
atomic<float> volume = 1.0f;
atomic<Array<float, 2>> channelVolumes = Array<float, 2>::filled(1);
unsigned int minimumPlaySamples = 4096;
Speaker(SpeakerId speakerId);
Json toJson() const;
};
typedef std::shared_ptr<Speaker> SpeakerPtr;
// Get pointer to the singleton Voice instance, if it exists. Otherwise,
// returns nullptr.
static Voice* singletonPtr();
// Gets reference to Voice singleton, throws VoiceException if root
// is not initialized.
static Voice& singleton();
Voice(ApplicationControllerPtr appController);
~Voice();
Voice(Voice const&) = delete;
Voice& operator=(Voice const&) = delete;
void init();
void loadJson(Json const& config, bool skipSave = false);
Json saveJson() const;
void save() const;
void scheduleSave();
// Sets the local speaker ID and returns the local speaker. Must be called upon loading into a world.
SpeakerPtr setLocalSpeaker(SpeakerId speakerId);
SpeakerPtr localSpeaker();
SpeakerPtr speaker(SpeakerId speakerId);
HashMap<SpeakerId, SpeakerPtr>& speakers();
List<Voice::SpeakerPtr> sortedSpeakers(bool onlyPlaying);
void clearSpeakers();
// Called when receiving input audio data from SDL, on its own thread.
void readAudioData(uint8_t* stream, int len);
// Called to mix voice audio with the game.
void mix(int16_t* buffer, size_t frames, unsigned channels);
typedef function<float(unsigned, Vec2F, float)> PositionalAttenuationFunction;
void update(PositionalAttenuationFunction positionalAttenuationFunction = {});
void setDeviceName(Maybe<String> device);
StringList availableDevices();
int send(DataStreamBuffer& out, size_t budget = 0);
bool receive(SpeakerPtr speaker, std::string_view view);
// Must be called every frame with input state, expires after 1s.
void setInput(bool input = true);
inline int encoderChannels() const { return (int)m_channelMode; }
static OpusDecoder* createDecoder(int channels);
static OpusEncoder* createEncoder(int channels);
private:
static Voice* s_singleton;
void resetEncoder();
void resetDevice();
void openDevice();
void closeDevice();
inline bool shouldEnableInput() const { return m_enabled && m_inputEnabled; }
bool playSpeaker(SpeakerPtr const& speaker, int channels);
void thread();
SpeakerId m_speakerId = 0;
SpeakerPtr m_clientSpeaker;
HashMap<SpeakerId, SpeakerPtr> m_speakers;
Mutex m_activeSpeakersMutex;
HashSet<SpeakerPtr> m_activeSpeakers;
OpusEncoderPtr m_encoder;
float m_outputVolume = 1.0f;
float m_inputVolume = 1.0f;
float m_threshold = -50.0f;
int64_t m_lastSentTime = 0;
int64_t m_lastInputTime = 0;
int64_t m_lastThresholdTime = 0;
int64_t m_nextSaveTime = 0;
bool m_enabled = true;
bool m_inputEnabled = false;
bool m_loopback = false;
int m_deviceChannels = 1;
bool m_deviceOpen = false;
Maybe<String> m_deviceName;
VoiceInputMode m_inputMode;
VoiceChannelMode m_channelMode;
ThreadFunction<void> m_thread;
Mutex m_threadMutex;
ConditionVariable m_threadCond;
atomic<bool> m_stopThread;
std::vector<int16_t> m_decodeBuffer;
std::vector<int16_t> m_resampleBuffer;
ApplicationControllerPtr m_applicationController;
struct EncodedChunk {
std::unique_ptr<unsigned char[]> data;
size_t size;
EncodedChunk(unsigned char* _data, size_t len) {
data.reset(_data);
size = len;
}
};
Mutex m_encodeMutex;
std::vector<ByteArray> m_encodedChunks;
size_t m_encodedChunksLength = 0;
Mutex m_captureMutex;
std::queue<VoiceAudioChunk> m_capturedChunks;
size_t m_capturedChunksFrames = 0;
};
}
#endif

View File

@ -0,0 +1,45 @@
#include "StarLuaConverters.hpp"
#include "StarVoiceLuaBindings.hpp"
#include "StarVoice.hpp"
namespace Star {
typedef Voice::SpeakerId SpeakerId;
LuaCallbacks LuaBindings::makeVoiceCallbacks() {
LuaCallbacks callbacks;
auto voice = Voice::singletonPtr();
callbacks.registerCallbackWithSignature<StringList>("devices", bind(&Voice::availableDevices, voice));
callbacks.registerCallback( "getSettings", [voice]() -> Json { return voice->saveJson(); });
callbacks.registerCallback("mergeSettings", [voice](Json const& settings) { voice->loadJson(settings); });
// i have an alignment addiction i'm so sorry
callbacks.registerCallback("setSpeakerMuted", [voice](SpeakerId speakerId, bool muted) { voice->speaker(speakerId)->muted = muted; });
callbacks.registerCallback( "speakerMuted", [voice](SpeakerId speakerId) { return (bool)voice->speaker(speakerId)->muted; });
// it just looks so neat to me!!
callbacks.registerCallback("setSpeakerVolume", [voice](SpeakerId speakerId, float volume) { voice->speaker(speakerId)->volume = volume; });
callbacks.registerCallback( "speakerVolume", [voice](SpeakerId speakerId) { return (float)voice->speaker(speakerId)->volume; });
callbacks.registerCallback("speakerPosition", [voice](SpeakerId speakerId) { return voice->speaker(speakerId)->position; });
callbacks.registerCallback("speaker", [voice](Maybe<SpeakerId> speakerId) {
if (speakerId)
return voice->speaker(*speakerId)->toJson();
else
return voice->localSpeaker()->toJson();
});
callbacks.registerCallback("speakers", [voice](Maybe<bool> onlyPlaying) -> List<Json> {
List<Json> list;
for (auto& speaker : voice->sortedSpeakers(onlyPlaying.value(true)))
list.append(speaker->toJson());
return list;
});
return callbacks;
}
}

View File

@ -0,0 +1,16 @@
#ifndef STAR_VOICE_LUA_BINDINGS_HPP
#define STAR_VOICE_LUA_BINDINGS_HPP
#include "StarLua.hpp"
namespace Star {
STAR_CLASS(Voice);
namespace LuaBindings {
LuaCallbacks makeVoiceCallbacks();
}
}
#endif

View File

@ -0,0 +1,23 @@
#include "StarVoiceSettingsMenu.hpp"
#include "StarVoiceLuaBindings.hpp"
namespace Star {
VoiceSettingsMenu::VoiceSettingsMenu(Json const& config) : BaseScriptPane(config) {
m_script.setLuaRoot(make_shared<LuaRoot>());
m_script.addCallbacks("voice", LuaBindings::makeVoiceCallbacks());
}
void VoiceSettingsMenu::show() {
BaseScriptPane::show();
}
void VoiceSettingsMenu::displayed() {
BaseScriptPane::displayed();
}
void VoiceSettingsMenu::dismissed() {
BaseScriptPane::dismissed();
}
}

View File

@ -0,0 +1,24 @@
#ifndef STAR_VOICE_SETTINGS_MENU_HPP
#define STAR_VOICE_SETTINGS_MENU_HPP
#include "StarBaseScriptPane.hpp"
namespace Star {
STAR_CLASS(VoiceSettingsMenu);
class VoiceSettingsMenu : public BaseScriptPane {
public:
VoiceSettingsMenu(Json const& config);
virtual void show() override;
void displayed() override;
void dismissed() override;
private:
};
}
#endif

View File

@ -316,10 +316,6 @@ Input::InputState* Input::bindStatePtr(String const& categoryId, String const& b
return nullptr; return nullptr;
} }
Input::InputState* Input::inputStatePtr(InputVariant key) {
return m_inputStates.ptr(key);
}
Input* Input::s_singleton; Input* Input::s_singleton;
Input* Input::singletonPtr() { Input* Input::singletonPtr() {
@ -361,28 +357,17 @@ List<std::pair<InputEvent, bool>> const& Input::inputEventsThisFrame() const {
void Input::reset() { void Input::reset() {
m_inputEvents.resize(0); // keeps reserved memory m_inputEvents.clear();
{ auto eraseCond = [](auto& p) {
auto it = m_inputStates.begin(); if (p.second.held)
while (it != m_inputStates.end()) { p.second.reset();
if (it->second.held) { return !p.second.held;
it->second.reset(); };
++it;
}
else it = m_inputStates.erase(it);
}
}
{ eraseWhere(m_keyStates, eraseCond);
auto it = m_bindStates.begin(); eraseWhere(m_mouseStates, eraseCond);
while (it != m_bindStates.end()) { eraseWhere(m_controllerStates, eraseCond);
if (it->second.held) { eraseWhere(m_bindStates, eraseCond);
it->second.reset();
++it;
}
else it = m_bindStates.erase(it);
}
}
} }
void Input::update() { void Input::update() {
@ -397,7 +382,10 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) {
m_pressedMods |= *keyToMod; m_pressedMods |= *keyToMod;
if (!gameProcessed && !m_textInputActive) { if (!gameProcessed && !m_textInputActive) {
m_inputStates[keyDown->key].press(); auto& state = m_keyStates[keyDown->key];
if (keyToMod)
state.mods |= *keyToMod;
state.press();
if (auto binds = m_bindMappings.ptr(keyDown->key)) { if (auto binds = m_bindMappings.ptr(keyDown->key)) {
for (auto bind : filterBindEntries(*binds, keyDown->mods)) for (auto bind : filterBindEntries(*binds, keyDown->mods))
@ -410,8 +398,11 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) {
m_pressedMods &= ~*keyToMod; m_pressedMods &= ~*keyToMod;
// We need to be able to release input even when gameProcessed is true, but only if it's already down. // We need to be able to release input even when gameProcessed is true, but only if it's already down.
if (auto state = m_inputStates.ptr(keyUp->key)) if (auto state = m_keyStates.ptr(keyUp->key)) {
if (keyToMod)
state->mods &= ~*keyToMod;
state->release(); state->release();
}
if (auto binds = m_bindMappings.ptr(keyUp->key)) { if (auto binds = m_bindMappings.ptr(keyUp->key)) {
for (auto& bind : *binds) { for (auto& bind : *binds) {
@ -421,7 +412,9 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) {
} }
} else if (auto mouseDown = input.ptr<MouseButtonDownEvent>()) { } else if (auto mouseDown = input.ptr<MouseButtonDownEvent>()) {
if (!gameProcessed) { if (!gameProcessed) {
m_inputStates[mouseDown->mouseButton].press(); auto& state = m_mouseStates[mouseDown->mouseButton];
state.pressPositions.append(mouseDown->mousePosition);
state.press();
if (auto binds = m_bindMappings.ptr(mouseDown->mouseButton)) { if (auto binds = m_bindMappings.ptr(mouseDown->mouseButton)) {
for (auto bind : filterBindEntries(*binds, m_pressedMods)) for (auto bind : filterBindEntries(*binds, m_pressedMods))
@ -429,8 +422,10 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) {
} }
} }
} else if (auto mouseUp = input.ptr<MouseButtonUpEvent>()) { } else if (auto mouseUp = input.ptr<MouseButtonUpEvent>()) {
if (auto state = m_inputStates.ptr(mouseUp->mouseButton)) if (auto state = m_mouseStates.ptr(mouseUp->mouseButton)) {
state->releasePositions.append(mouseUp->mousePosition);
state->release(); state->release();
}
if (auto binds = m_bindMappings.ptr(mouseUp->mouseButton)) { if (auto binds = m_bindMappings.ptr(mouseUp->mouseButton)) {
for (auto& bind : *binds) { for (auto& bind : *binds) {
@ -497,10 +492,10 @@ void Input::setTextInputActive(bool active) {
} }
Maybe<unsigned> Input::bindDown(String const& categoryId, String const& bindId) { Maybe<unsigned> Input::bindDown(String const& categoryId, String const& bindId) {
if (auto state = bindStatePtr(categoryId, bindId)) if (auto state = bindStatePtr(categoryId, bindId)) {
if (state->presses) if (state->presses)
return state->presses; return state->presses;
}
return {}; return {};
} }
@ -512,10 +507,52 @@ bool Input::bindHeld(String const& categoryId, String const& bindId) {
} }
Maybe<unsigned> Input::bindUp(String const& categoryId, String const& bindId) { Maybe<unsigned> Input::bindUp(String const& categoryId, String const& bindId) {
if (auto state = bindStatePtr(categoryId, bindId)) if (auto state = bindStatePtr(categoryId, bindId)) {
if (state->releases) if (state->releases)
return state->releases; return state->releases;
}
return {};
}
Maybe<unsigned> Input::keyDown(Key key, Maybe<KeyMod> keyMod) {
if (auto state = m_keyStates.ptr(key)) {
if (state->presses && (!keyMod || compareKeyMod(*keyMod, state->mods)))
return state->presses;
}
return {};
}
bool Input::keyHeld(Key key) {
auto state = m_keyStates.ptr(key);
return state && state->held;
}
Maybe<unsigned> Input::keyUp(Key key) {
if (auto state = m_keyStates.ptr(key)) {
if (state->releases)
return state->releases;
}
return {};
}
Maybe<List<Vec2I>> Input::mouseDown(MouseButton button) {
if (auto state = m_mouseStates.ptr(button)) {
if (state->presses)
return state->pressPositions;
}
return {};
}
bool Input::mouseHeld(MouseButton button) {
auto state = m_mouseStates.ptr(button);
return state && state->held;
}
Maybe<List<Vec2I>> Input::mouseUp(MouseButton button) {
if (auto state = m_mouseStates.ptr(button)) {
if (state->releases)
return state->releasePositions;
}
return {}; return {};
} }

View File

@ -117,6 +117,17 @@ public:
inline void release() { released = ++releases; held = false; } inline void release() { released = ++releases; held = false; }
}; };
struct KeyInputState : InputState {
KeyMod mods = KeyMod::NoMod;
};
struct MouseInputState : InputState {
List<Vec2I> pressPositions;
List<Vec2I> releasePositions;
};
typedef InputState ControllerInputState;
// Get pointer to the singleton Input instance, if it exists. Otherwise, // Get pointer to the singleton Input instance, if it exists. Otherwise,
// returns nullptr. // returns nullptr.
static Input* singletonPtr(); static Input* singletonPtr();
@ -152,6 +163,14 @@ public:
bool bindHeld(String const& categoryId, String const& bindId); bool bindHeld(String const& categoryId, String const& bindId);
Maybe<unsigned> bindUp (String const& categoryId, String const& bindId); Maybe<unsigned> bindUp (String const& categoryId, String const& bindId);
Maybe<unsigned> keyDown(Key key, Maybe<KeyMod> keyMod);
bool keyHeld(Key key);
Maybe<unsigned> keyUp (Key key);
Maybe<List<Vec2I>> mouseDown(MouseButton button);
bool mouseHeld(MouseButton button);
Maybe<List<Vec2I>> mouseUp (MouseButton button);
void resetBinds(String const& categoryId, String const& bindId); void resetBinds(String const& categoryId, String const& bindId);
void setBinds(String const& categoryId, String const& bindId, Json const& binds); void setBinds(String const& categoryId, String const& bindId, Json const& binds);
Json getDefaultBinds(String const& categoryId, String const& bindId); Json getDefaultBinds(String const& categoryId, String const& bindId);
@ -163,7 +182,6 @@ private:
BindEntry& bindEntry(String const& categoryId, String const& bindId); BindEntry& bindEntry(String const& categoryId, String const& bindId);
InputState* bindStatePtr(String const& categoryId, String const& bindId); InputState* bindStatePtr(String const& categoryId, String const& bindId);
InputState* inputStatePtr(InputVariant key);
static Input* s_singleton; static Input* s_singleton;
@ -179,7 +197,9 @@ private:
// Per-frame input state maps. // Per-frame input state maps.
//Input states //Input states
HashMap<InputVariant, InputState> m_inputStates; HashMap<Key, KeyInputState> m_keyStates;
HashMap<MouseButton, MouseInputState> m_mouseStates;
HashMap<ControllerButton, ControllerInputState> m_controllerStates;
//Bind states //Bind states
HashMap<BindEntry const*, InputState> m_bindStates; HashMap<BindEntry const*, InputState> m_bindStates;

View File

@ -383,6 +383,15 @@ void NetworkedAnimator::setGlobalTag(String tagName, String tagValue) {
m_globalTags.set(move(tagName), move(tagValue)); m_globalTags.set(move(tagName), move(tagValue));
} }
void NetworkedAnimator::removeGlobalTag(String const& tagName) {
m_globalTags.remove(tagName);
}
String const* NetworkedAnimator::globalTagPtr(String const& tagName) const {
return m_globalTags.ptr(tagName);
}
void NetworkedAnimator::setPartTag(String const& partType, String tagName, String tagValue) { void NetworkedAnimator::setPartTag(String const& partType, String tagName, String tagValue) {
m_partTags[partType].set(move(tagName), move(tagValue)); m_partTags[partType].set(move(tagName), move(tagValue));
} }

View File

@ -115,6 +115,8 @@ public:
// Drawables can also have a <frame> tag which will be set to whatever the // Drawables can also have a <frame> tag which will be set to whatever the
// current state frame is (1 indexed, so the first frame is 1). // current state frame is (1 indexed, so the first frame is 1).
void setGlobalTag(String tagName, String tagValue); void setGlobalTag(String tagName, String tagValue);
void removeGlobalTag(String const& tagName);
String const* globalTagPtr(String const& tagName) const;
void setPartTag(String const& partType, String tagName, String tagValue); void setPartTag(String const& partType, String tagName, String tagValue);
void setProcessingDirectives(Directives const& directives); void setProcessingDirectives(Directives const& directives);

View File

@ -2464,4 +2464,53 @@ Vec2F Player::cameraPosition() {
return position(); return position();
} }
NetworkedAnimatorPtr Player::effectsAnimator() {
return m_effectsAnimator;
}
const String secretProprefix = "\0JsonProperty\0"s;
Maybe<StringView> Player::getSecretPropertyView(String const& name) const {
if (auto tag = m_effectsAnimator->globalTagPtr(secretProprefix + name)) {
auto& view = tag->utf8();
DataStreamExternalBuffer buffer(view.data(), view.size());
try {
uint8_t typeIndex = buffer.read<uint8_t>() - 1;
if ((Json::Type)typeIndex == Json::Type::String) {
size_t len = buffer.readVlqU();
size_t pos = buffer.pos();
if (pos + len == buffer.size())
return StringView(buffer.ptr() + pos, len);
}
}
catch (StarException const& e) {}
}
return {};
}
Json Player::getSecretProperty(String const& name, Json defaultValue) const {
if (auto tag = m_effectsAnimator->globalTagPtr(secretProprefix + name)) {
DataStreamExternalBuffer buffer(tag->utf8Ptr(), tag->utf8Size());
try
{ return buffer.read<Json>(); }
catch (StarException const& e)
{ Logger::error("Exception reading secret player property '{}': {}", name, e.what()); }
}
return move(defaultValue);
}
void Player::setSecretProperty(String const& name, Json const& value) {
if (value) {
DataStreamBuffer ds;
ds.write(value);
auto& data = ds.data();
m_effectsAnimator->setGlobalTag(secretProprefix + name, String(data.ptr(), data.size()));
}
else
m_effectsAnimator->removeGlobalTag(secretProprefix + name);
}
} }

View File

@ -447,6 +447,28 @@ public:
using Entity::setTeam; using Entity::setTeam;
NetworkedAnimatorPtr effectsAnimator();
// We need to store ephemeral/large/always-changing networked properties that other clients can read. Candidates:
// genericProperties:
// Non-starter, is not networked.
// statusProperties:
// Nope! Changes to the status properties aren't networked efficiently - one change resends the whole map.
// We can't fix that because it would break compatibility with vanilla servers.
// effectsAnimator's globalTags:
// Cursed, but viable.
// Efficient networking due to using a NetElementMapWrapper.
// Unfortunately values are Strings, so to work with Json we need to serialize/deserialize. Whatever.
// Additionally, this is compatible with vanilla networking.
// I call this a 'secret property'.
// If the secret property exists as a serialized Json string, returns a view to it without deserializing.
Maybe<StringView> getSecretPropertyView(String const& name) const;
// Gets a secret Json property. It will be de-serialized.
Json getSecretProperty(String const& name, Json defaultValue = Json()) const;
// Sets a secret Json property. It will be serialized.
void setSecretProperty(String const& name, Json const& value);
private: private:
enum class State { enum class State {
Idle, Idle,

View File

@ -30,6 +30,7 @@ UniverseClient::UniverseClient(PlayerStoragePtr playerStorage, StatisticsPtr sta
m_playerStorage = move(playerStorage); m_playerStorage = move(playerStorage);
m_statistics = move(statistics); m_statistics = move(statistics);
m_pause = false; m_pause = false;
m_luaRoot = make_shared<LuaRoot>();
reset(); reset();
} }
@ -86,7 +87,7 @@ Maybe<String> UniverseClient::connect(UniverseConnection connection, bool allowA
return String(strf("Join failed! Server does not support connections with protocol version {}", StarProtocolVersion)); return String(strf("Join failed! Server does not support connections with protocol version {}", StarProtocolVersion));
connection.pushSingle(make_shared<ClientConnectPacket>(Root::singleton().assets()->digest(), allowAssetsMismatch, m_mainPlayer->uuid(), m_mainPlayer->name(), connection.pushSingle(make_shared<ClientConnectPacket>(Root::singleton().assets()->digest(), allowAssetsMismatch, m_mainPlayer->uuid(), m_mainPlayer->name(),
m_mainPlayer->species(), m_playerStorage->loadShipData(m_mainPlayer->uuid()), ShipUpgrades(m_mainPlayer->shipUpgrades()), m_mainPlayer->species(), m_playerStorage->loadShipData(m_mainPlayer->uuid()), m_mainPlayer->shipUpgrades(),
m_mainPlayer->log()->introComplete(), account)); m_mainPlayer->log()->introComplete(), account));
connection.sendAll(timeout); connection.sendAll(timeout);
@ -219,8 +220,11 @@ void UniverseClient::update() {
m_statistics->update(); m_statistics->update();
if (!m_pause) if (!m_pause) {
m_worldClient->update(); m_worldClient->update();
for (auto& p : m_scriptContexts)
p.second->update();
}
m_connection->push(m_worldClient->getOutgoingPackets()); m_connection->push(m_worldClient->getOutgoingPackets());
if (!m_pause) if (!m_pause)
@ -444,6 +448,28 @@ void UniverseClient::setLuaCallbacks(String const& groupName, LuaCallbacks const
m_worldClient->setLuaCallbacks(groupName, callbacks); m_worldClient->setLuaCallbacks(groupName, callbacks);
} }
void UniverseClient::startLua() {
auto assets = Root::singleton().assets();
for (auto& p : assets->json("/client.config:universeScriptContexts").toObject()) {
auto scriptComponent = make_shared<ScriptComponent>();
scriptComponent->setLuaRoot(m_luaRoot);
scriptComponent->setScripts(jsonToStringList(p.second.toArray()));
for (auto& pair : m_luaCallbacks)
scriptComponent->addCallbacks(pair.first, pair.second);
m_scriptContexts.set(p.first, scriptComponent);
scriptComponent->init();
}
}
void UniverseClient::stopLua() {
for (auto& p : m_scriptContexts)
p.second->uninit();
m_scriptContexts.clear();
}
ClockConstPtr UniverseClient::universeClock() const { ClockConstPtr UniverseClient::universeClock() const {
return m_universeClock; return m_universeClock;
} }
@ -539,6 +565,8 @@ void UniverseClient::handlePackets(List<PacketPtr> const& packets) {
} }
void UniverseClient::reset() { void UniverseClient::reset() {
stopLua();
m_universeClock.reset(); m_universeClock.reset();
m_worldClient.reset(); m_worldClient.reset();
m_celestialDatabase.reset(); m_celestialDatabase.reset();

View File

@ -10,6 +10,7 @@
#include "StarAiTypes.hpp" #include "StarAiTypes.hpp"
#include "StarSky.hpp" #include "StarSky.hpp"
#include "StarUniverseConnection.hpp" #include "StarUniverseConnection.hpp"
#include "StarLuaComponents.hpp"
namespace Star { namespace Star {
@ -29,8 +30,8 @@ STAR_CLASS(CelestialDatabase);
STAR_CLASS(JsonRpcInterface); STAR_CLASS(JsonRpcInterface);
STAR_CLASS(TeamClient); STAR_CLASS(TeamClient);
STAR_CLASS(QuestManager); STAR_CLASS(QuestManager);
STAR_CLASS(UniverseClient); STAR_CLASS(UniverseClient);
STAR_CLASS(LuaRoot);
class UniverseClient { class UniverseClient {
public: public:
@ -86,6 +87,8 @@ public:
uint16_t maxPlayers(); uint16_t maxPlayers();
void setLuaCallbacks(String const& groupName, LuaCallbacks const& callbacks); void setLuaCallbacks(String const& groupName, LuaCallbacks const& callbacks);
void startLua();
void stopLua();
ClockConstPtr universeClock() const; ClockConstPtr universeClock() const;
CelestialLogConstPtr celestialLog() const; CelestialLogConstPtr celestialLog() const;
@ -141,6 +144,12 @@ private:
List<ChatReceivedMessage> m_pendingMessages; List<ChatReceivedMessage> m_pendingMessages;
Maybe<String> m_disconnectReason; Maybe<String> m_disconnectReason;
LuaRootPtr m_luaRoot;
typedef LuaUpdatableComponent<LuaBaseComponent> ScriptComponent;
typedef shared_ptr<ScriptComponent> ScriptComponentPtr;
StringMap<ScriptComponentPtr> m_scriptContexts;
}; };
} }

View File

@ -20,11 +20,14 @@
#include "StarWorldTemplate.hpp" #include "StarWorldTemplate.hpp"
#include "StarStoredFunctions.hpp" #include "StarStoredFunctions.hpp"
#include "StarInspectableEntity.hpp" #include "StarInspectableEntity.hpp"
#include "StarCurve25519.hpp"
namespace Star { namespace Star {
const float WorldClient::DropDist = 6.0f; const std::string SECRET_BROADCAST_PUBLIC_KEY = "SecretBroadcastPublicKey";
const std::string SECRET_BROADCAST_PREFIX = "\0Broadcast\0"s;
const float WorldClient::DropDist = 6.0f;
WorldClient::WorldClient(PlayerPtr mainPlayer) { WorldClient::WorldClient(PlayerPtr mainPlayer) {
auto& root = Root::singleton(); auto& root = Root::singleton();
auto assets = root.assets(); auto assets = root.assets();
@ -792,7 +795,50 @@ void WorldClient::handleIncomingPackets(List<PacketPtr> const& packets) {
m_damageManager->pushRemoteDamageRequest(damage->remoteDamageRequest); m_damageManager->pushRemoteDamageRequest(damage->remoteDamageRequest);
} else if (auto damage = as<DamageNotificationPacket>(packet)) { } else if (auto damage = as<DamageNotificationPacket>(packet)) {
std::string_view view(damage->remoteDamageNotification.damageNotification.targetMaterialKind.utf8());
static const size_t FULL_SIZE = SECRET_BROADCAST_PREFIX.size() + Curve25519::SignatureSize;
static const std::string LEGACY_VOICE_PREFIX = "data\0voice\0"s;
if (view.size() >= FULL_SIZE && view.rfind(SECRET_BROADCAST_PREFIX, 0) != NPos) {
// this is actually a secret broadcast!!
if (auto player = m_entityMap->get<Player>(damage->remoteDamageNotification.sourceEntityId)) {
if (auto publicKey = player->getSecretPropertyView(SECRET_BROADCAST_PUBLIC_KEY)) {
if (publicKey->utf8Size() == Curve25519::PublicKeySize) {
auto signature = view.substr(SECRET_BROADCAST_PREFIX.size(), Curve25519::SignatureSize);
auto rawBroadcast = view.substr(FULL_SIZE);
if (Curve25519::verify(
(uint8_t const*)signature.data(),
(uint8_t const*)publicKey->utf8Ptr(),
(void*)rawBroadcast.data(),
rawBroadcast.size()
)) {
handleSecretBroadcast(player, rawBroadcast);
}
}
}
}
}
else if (view.size() > 75 && view.rfind(LEGACY_VOICE_PREFIX, 0) != NPos) {
// this is a StarExtensions voice packet
// (remove this and stop transmitting like this once most SE features are ported over)
if (auto player = m_entityMap->get<Player>(damage->remoteDamageNotification.sourceEntityId)) {
if (auto publicKey = player->effectsAnimator()->globalTagPtr("\0SE_VOICE_SIGNING_KEY"s)) {
auto rawData = view.substr(75);
if (m_broadcastCallback && Curve25519::verify(
(uint8_t const*)view.data() + LEGACY_VOICE_PREFIX.size(),
(uint8_t const*)publicKey->utf8Ptr(),
(void*)rawData.data(),
rawData.size()
)) {
m_broadcastCallback(player, "Voice\0"s + rawData);
}
}
}
}
else {
m_damageManager->pushRemoteDamageNotification(damage->remoteDamageNotification); m_damageManager->pushRemoteDamageNotification(damage->remoteDamageNotification);
}
} else if (auto entityMessagePacket = as<EntityMessagePacket>(packet)) { } else if (auto entityMessagePacket = as<EntityMessagePacket>(packet)) {
EntityPtr entity; EntityPtr entity;
@ -917,6 +963,14 @@ void WorldClient::update() {
} }
} }
// Secret broadcasts are transmitted through DamageNotifications for vanilla server compatibility.
// Because DamageNotification packets are spoofable, we have to sign the data so other clients can validate that it is legitimate.
auto& publicKey = Curve25519::publicKey();
String publicKeyString((const char*)publicKey.data(), publicKey.size());
m_mainPlayer->setSecretProperty(SECRET_BROADCAST_PUBLIC_KEY, publicKeyString);
// Temporary: Backwards compatibility with StarExtensions
m_mainPlayer->effectsAnimator()->setGlobalTag("\0SE_VOICE_SIGNING_KEY"s, publicKeyString);
++m_currentStep; ++m_currentStep;
//m_interpolationTracker.update(m_currentStep); //m_interpolationTracker.update(m_currentStep);
m_interpolationTracker.update(Time::monotonicTime()); m_interpolationTracker.update(Time::monotonicTime());
@ -1176,6 +1230,10 @@ void WorldClient::waitForLighting() {
MutexLocker lock(m_lightingMutex); MutexLocker lock(m_lightingMutex);
} }
WorldClient::BroadcastCallback& WorldClient::broadcastCallback() {
return m_broadcastCallback;
}
bool WorldClient::isTileProtected(Vec2I const& pos) const { bool WorldClient::isTileProtected(Vec2I const& pos) const {
if (!inWorld()) if (!inWorld())
return true; return true;
@ -1847,6 +1905,35 @@ void WorldClient::connectWire(WireConnection const& output, WireConnection const
m_outgoingPackets.append(make_shared<ConnectWirePacket>(output, input)); m_outgoingPackets.append(make_shared<ConnectWirePacket>(output, input));
} }
bool WorldClient::sendSecretBroadcast(StringView broadcast, bool raw) {
if (!inWorld() || !m_mainPlayer || !m_mainPlayer->getSecretPropertyView(SECRET_BROADCAST_PUBLIC_KEY))
return false;
auto signature = Curve25519::sign((void*)broadcast.utf8Ptr(), broadcast.utf8Size());
auto damageNotification = make_shared<DamageNotificationPacket>();
auto& remDmg = damageNotification->remoteDamageNotification;
auto& dmg = remDmg.damageNotification;
dmg.targetEntityId = dmg.sourceEntityId = remDmg.sourceEntityId = m_mainPlayer->entityId();
dmg.damageDealt = dmg.healthLost = 0.0f;
dmg.hitType = HitType::Hit;
dmg.damageSourceKind = "nodamage";
dmg.targetMaterialKind = raw ? broadcast : strf("{}{}{}", SECRET_BROADCAST_PREFIX, StringView((char*)&signature, sizeof(signature)), broadcast);
dmg.position = m_mainPlayer->position();
m_outgoingPackets.emplace_back(move(damageNotification));
return true;
}
bool WorldClient::handleSecretBroadcast(PlayerPtr player, StringView broadcast) {
if (m_broadcastCallback)
return m_broadcastCallback(player, broadcast);
else
return false;
}
void WorldClient::ClientRenderCallback::addDrawable(Drawable drawable, EntityRenderLayer renderLayer) { void WorldClient::ClientRenderCallback::addDrawable(Drawable drawable, EntityRenderLayer renderLayer) {
drawables[renderLayer].append(move(drawable)); drawables[renderLayer].append(move(drawable));
} }

View File

@ -150,6 +150,12 @@ public:
void disconnectAllWires(Vec2I wireEntityPosition, WireNode const& node); void disconnectAllWires(Vec2I wireEntityPosition, WireNode const& node);
void connectWire(WireConnection const& output, WireConnection const& input); void connectWire(WireConnection const& output, WireConnection const& input);
// Functions for sending broadcast messages to other players that can receive them,
// on completely vanilla servers by smuggling it through a DamageNotification.
// It's cursed as fuck, but it works.
bool sendSecretBroadcast(StringView broadcast, bool raw = false);
bool handleSecretBroadcast(PlayerPtr player, StringView broadcast);
List<ChatAction> pullPendingChatActions(); List<ChatAction> pullPendingChatActions();
WorldStructure const& centralStructure() const; WorldStructure const& centralStructure() const;
@ -160,6 +166,8 @@ public:
void waitForLighting(); void waitForLighting();
typedef std::function<bool(PlayerPtr, StringView)> BroadcastCallback;
BroadcastCallback& broadcastCallback();
private: private:
static const float DropDist; static const float DropDist;
@ -339,6 +347,8 @@ private:
HashMap<Uuid, RpcPromiseKeeper<InteractAction>> m_entityInteractionResponses; HashMap<Uuid, RpcPromiseKeeper<InteractAction>> m_entityInteractionResponses;
List<PhysicsForceRegion> m_forceRegions; List<PhysicsForceRegion> m_forceRegions;
BroadcastCallback m_broadcastCallback;
}; };
} }

View File

@ -13,6 +13,23 @@ LuaCallbacks LuaBindings::makeInputCallbacks() {
callbacks.registerCallbackWithSignature<bool, String, String>("bindHeld", bind(mem_fn(&Input::bindHeld), input, _1, _2)); callbacks.registerCallbackWithSignature<bool, String, String>("bindHeld", bind(mem_fn(&Input::bindHeld), input, _1, _2));
callbacks.registerCallbackWithSignature<Maybe<unsigned>, String, String>("bindUp", bind(mem_fn(&Input::bindUp), input, _1, _2)); callbacks.registerCallbackWithSignature<Maybe<unsigned>, String, String>("bindUp", bind(mem_fn(&Input::bindUp), input, _1, _2));
callbacks.registerCallback("keyDown", [input](String const& keyName, Maybe<StringList>& const modNames) -> Maybe<unsigned> {
Key key = KeyNames.getLeft(keyName);
Maybe<KeyMod> mod;
if (modNames) {
mod = KeyMod::NoMod;
for (auto& modName : *modNames)
*mod |= KeyModNames.getLeft(modName);
}
return input->keyDown(key, mod);
});
callbacks.registerCallback("keyHeld", [input](String const& keyName) -> bool { return input->keyHeld(KeyNames.getLeft(keyName)); });
callbacks.registerCallback("keyUp", [input](String const& keyName) -> Maybe<unsigned> { return input->keyUp( KeyNames.getLeft(keyName)); });
callbacks.registerCallback("mouseDown", [input](String const& buttonName) -> Maybe<List<Vec2I>> { return input->mouseDown(MouseButtonNames.getLeft(buttonName)); });
callbacks.registerCallback("mouseHeld", [input](String const& buttonName) -> bool { return input->mouseHeld(MouseButtonNames.getLeft(buttonName)); });
callbacks.registerCallback("mouseUp", [input](String const& buttonName) -> Maybe<List<Vec2I>> { return input->mouseUp( MouseButtonNames.getLeft(buttonName)); });
callbacks.registerCallbackWithSignature<void, String, String>("resetBinds", bind(mem_fn(&Input::resetBinds), input, _1, _2)); callbacks.registerCallbackWithSignature<void, String, String>("resetBinds", bind(mem_fn(&Input::resetBinds), input, _1, _2));
callbacks.registerCallbackWithSignature<void, String, String, Json>("setBinds", bind(mem_fn(&Input::setBinds), input, _1, _2, _3)); callbacks.registerCallbackWithSignature<void, String, String, Json>("setBinds", bind(mem_fn(&Input::setBinds), input, _1, _2, _3));
callbacks.registerCallbackWithSignature<Json, String, String>("getDefaultBinds", bind(mem_fn(&Input::getDefaultBinds), input, _1, _2)); callbacks.registerCallbackWithSignature<Json, String, String>("getDefaultBinds", bind(mem_fn(&Input::getDefaultBinds), input, _1, _2));

View File

@ -106,6 +106,11 @@ LuaContext LuaRoot::createContext(StringList const& scriptPaths) {
} }
}); });
newContext.set("loadstring", m_luaEngine->createFunction([newContext](String const& source, Maybe<String> const& name, Maybe<LuaValue> const& env) -> LuaFunction {
String functionName = name ? strf("loadstring: {}", *name) : "loadstring";
return newContext.engine().createFunctionFromSource(newContext.handleIndex(), source.utf8Ptr(), source.utf8Size(), functionName.utf8Ptr());
}));
for (auto const& scriptPath : scriptPaths) for (auto const& scriptPath : scriptPaths)
cache->loadContextScript(newContext, scriptPath); cache->loadContextScript(newContext, scriptPath);

View File

@ -88,20 +88,21 @@ bool CanvasWidget::sendEvent(InputEvent const& event) {
return false; return false;
auto& context = GuiContext::singleton(); auto& context = GuiContext::singleton();
int interfaceScale = m_ignoreInterfaceScale ? 1 : context.interfaceScale();
if (auto mouseButtonDown = event.ptr<MouseButtonDownEvent>()) { if (auto mouseButtonDown = event.ptr<MouseButtonDownEvent>()) {
if (inMember(*context.mousePosition(event)) && m_captureMouse) { if (inMember(*context.mousePosition(event, interfaceScale)) && m_captureMouse) {
m_clickEvents.append({*context.mousePosition(event) - screenPosition(), mouseButtonDown->mouseButton, true}); m_clickEvents.append({*context.mousePosition(event, interfaceScale) - screenPosition(), mouseButtonDown->mouseButton, true});
m_clickEvents.limitSizeBack(MaximumEventBuffer); m_clickEvents.limitSizeBack(MaximumEventBuffer);
return true; return true;
} }
} else if (auto mouseButtonUp = event.ptr<MouseButtonUpEvent>()) { } else if (auto mouseButtonUp = event.ptr<MouseButtonUpEvent>()) {
if (m_captureMouse) { if (m_captureMouse) {
m_clickEvents.append({*context.mousePosition(event) - screenPosition(), mouseButtonUp->mouseButton, false}); m_clickEvents.append({*context.mousePosition(event, interfaceScale) - screenPosition(), mouseButtonUp->mouseButton, false});
m_clickEvents.limitSizeBack(MaximumEventBuffer); m_clickEvents.limitSizeBack(MaximumEventBuffer);
return true; return true;
} }
} else if (event.is<MouseMoveEvent>()) { } else if (event.is<MouseMoveEvent>()) {
m_mousePosition = *context.mousePosition(event) - screenPosition(); m_mousePosition = *context.mousePosition(event, interfaceScale) - screenPosition();
return false; return false;
} else if (auto keyDown = event.ptr<KeyDownEvent>()) { } else if (auto keyDown = event.ptr<KeyDownEvent>()) {
if (m_captureKeyboard) { if (m_captureKeyboard) {
@ -258,7 +259,7 @@ void CanvasWidget::renderTriangles(Vec2F const& renderingOffset, List<tuple<Vec2
void CanvasWidget::renderText(Vec2F const& renderingOffset, String const& s, TextPositioning const& position, unsigned fontSize, Vec4B const& color, FontMode mode, float lineSpacing, String const& font, String const& directives) { void CanvasWidget::renderText(Vec2F const& renderingOffset, String const& s, TextPositioning const& position, unsigned fontSize, Vec4B const& color, FontMode mode, float lineSpacing, String const& font, String const& directives) {
auto& context = GuiContext::singleton(); auto& context = GuiContext::singleton();
context.setFontProcessingDirectives(directives); context.setFontProcessingDirectives(directives);
context.setFontSize(fontSize); context.setFontSize(fontSize, m_ignoreInterfaceScale ? 1 : context.interfaceScale());
context.setFontColor(color); context.setFontColor(color);
context.setFontMode(mode); context.setFontMode(mode);
context.setFont(font); context.setFont(font);

View File

@ -98,9 +98,9 @@ void GuiContext::setInterfaceScale(int interfaceScale) {
m_interfaceScale = interfaceScale; m_interfaceScale = interfaceScale;
} }
Maybe<Vec2I> GuiContext::mousePosition(InputEvent const& event) const { Maybe<Vec2I> GuiContext::mousePosition(InputEvent const& event, int pixelRatio) const {
auto getInterfacePosition = [this](Vec2I pos) { auto getInterfacePosition = [pixelRatio](Vec2I pos) {
return Vec2I(pos) / interfaceScale(); return Vec2I(pos) / pixelRatio;
}; };
if (auto mouseMoveEvent = event.ptr<MouseMoveEvent>()) if (auto mouseMoveEvent = event.ptr<MouseMoveEvent>())
@ -115,6 +115,10 @@ Maybe<Vec2I> GuiContext::mousePosition(InputEvent const& event) const {
return {}; return {};
} }
Maybe<Vec2I> GuiContext::mousePosition(InputEvent const& event) const {
return mousePosition(event, interfaceScale());
}
Set<InterfaceAction> GuiContext::actions(InputEvent const& event) const { Set<InterfaceAction> GuiContext::actions(InputEvent const& event) const {
return m_keyBindings.actions(event); return m_keyBindings.actions(event);
} }

View File

@ -50,6 +50,7 @@ public:
int interfaceScale() const; int interfaceScale() const;
void setInterfaceScale(int interfaceScale); void setInterfaceScale(int interfaceScale);
Maybe<Vec2I> mousePosition(InputEvent const& event, int pixelRatio) const;
Maybe<Vec2I> mousePosition(InputEvent const& event) const; Maybe<Vec2I> mousePosition(InputEvent const& event) const;
Set<InterfaceAction> actions(InputEvent const& event) const; Set<InterfaceAction> actions(InputEvent const& event) const;

View File

@ -404,6 +404,10 @@ LuaCallbacks Pane::makePaneCallbacks() {
return this->removeChild(widgetName); return this->removeChild(widgetName);
}); });
callbacks.registerCallback("scale", []() -> int {
return GuiContext::singleton().interfaceScale();
});
return callbacks; return callbacks;
} }

Some files were not shown because too many files have changed in this diff Show More