298 lines
7.2 KiB
Go
298 lines
7.2 KiB
Go
package p256k1
|
|
|
|
import (
|
|
"errors"
|
|
"unsafe"
|
|
)
|
|
|
|
// Context represents a secp256k1 context object that holds randomization data
|
|
// and callback functions for enhanced protection against side-channel leakage
|
|
type Context struct {
|
|
ecmultGenCtx EcmultGenContext
|
|
illegalCallback Callback
|
|
errorCallback Callback
|
|
declassify bool
|
|
}
|
|
|
|
// EcmultGenContext holds precomputed data for scalar multiplication with the generator
|
|
type EcmultGenContext struct {
|
|
built bool
|
|
// Precomputed table: prec[i][j] = (j+1) * 2^(i*4) * G
|
|
prec [64][16]GroupElementAffine
|
|
blindPoint GroupElementAffine // Blinding point for side-channel protection
|
|
}
|
|
|
|
// Context flags
|
|
const (
|
|
ContextNone = 0x01
|
|
ContextVerify = 0x01 | 0x0100 // Deprecated, treated as NONE
|
|
ContextSign = 0x01 | 0x0200 // Deprecated, treated as NONE
|
|
ContextDeclassify = 0x01 | 0x0400 // Testing flag
|
|
)
|
|
|
|
// Static context for basic operations (limited functionality)
|
|
var ContextStatic = &Context{
|
|
ecmultGenCtx: EcmultGenContext{built: false},
|
|
illegalCallback: defaultIllegalCallback,
|
|
errorCallback: defaultErrorCallback,
|
|
declassify: false,
|
|
}
|
|
|
|
// ContextCreate creates a new secp256k1 context object
|
|
func ContextCreate(flags uint) (ctx *Context, err error) {
|
|
// Validate flags
|
|
if (flags & 0xFF) != ContextNone {
|
|
return nil, errors.New("invalid flags")
|
|
}
|
|
|
|
ctx = &Context{
|
|
illegalCallback: defaultIllegalCallback,
|
|
errorCallback: defaultErrorCallback,
|
|
declassify: (flags & ContextDeclassify) != 0,
|
|
}
|
|
|
|
// Build the ecmult_gen context
|
|
err = ctx.ecmultGenCtx.build()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ctx, nil
|
|
}
|
|
|
|
// ContextClone creates a copy of a context
|
|
func ContextClone(ctx *Context) (newCtx *Context, err error) {
|
|
if ctx == ContextStatic {
|
|
return nil, errors.New("cannot clone static context")
|
|
}
|
|
|
|
newCtx = &Context{
|
|
ecmultGenCtx: ctx.ecmultGenCtx,
|
|
illegalCallback: ctx.illegalCallback,
|
|
errorCallback: ctx.errorCallback,
|
|
declassify: ctx.declassify,
|
|
}
|
|
|
|
return newCtx, nil
|
|
}
|
|
|
|
// ContextDestroy destroys a context object
|
|
func ContextDestroy(ctx *Context) {
|
|
if ctx == nil || ctx == ContextStatic {
|
|
return
|
|
}
|
|
|
|
ctx.ecmultGenCtx.clear()
|
|
ctx.illegalCallback = Callback{}
|
|
ctx.errorCallback = Callback{}
|
|
}
|
|
|
|
// ContextSetIllegalCallback sets the illegal argument callback
|
|
func ContextSetIllegalCallback(ctx *Context, fn func(string, interface{}), data interface{}) error {
|
|
if ctx == ContextStatic {
|
|
return errors.New("cannot set callback on static context")
|
|
}
|
|
|
|
if fn == nil {
|
|
ctx.illegalCallback = defaultIllegalCallback
|
|
} else {
|
|
ctx.illegalCallback = Callback{Fn: fn, Data: data}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ContextSetErrorCallback sets the error callback
|
|
func ContextSetErrorCallback(ctx *Context, fn func(string, interface{}), data interface{}) error {
|
|
if ctx == ContextStatic {
|
|
return errors.New("cannot set callback on static context")
|
|
}
|
|
|
|
if fn == nil {
|
|
ctx.errorCallback = defaultErrorCallback
|
|
} else {
|
|
ctx.errorCallback = Callback{Fn: fn, Data: data}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ContextRandomize randomizes the context for enhanced side-channel protection
|
|
func ContextRandomize(ctx *Context, seed32 []byte) error {
|
|
if ctx == ContextStatic {
|
|
return errors.New("cannot randomize static context")
|
|
}
|
|
|
|
if !ctx.ecmultGenCtx.built {
|
|
return errors.New("context not properly initialized")
|
|
}
|
|
|
|
if seed32 != nil && len(seed32) != 32 {
|
|
return errors.New("seed must be 32 bytes or nil")
|
|
}
|
|
|
|
// Apply randomization to the ecmult_gen context
|
|
return ctx.ecmultGenCtx.blind(seed32)
|
|
}
|
|
|
|
// isProper checks if a context is proper (not static and properly initialized)
|
|
func (ctx *Context) isProper() bool {
|
|
return ctx != ContextStatic && ctx.ecmultGenCtx.built
|
|
}
|
|
|
|
// EcmultGenContext methods
|
|
|
|
// build initializes the ecmult_gen context with precomputed values
|
|
func (ctx *EcmultGenContext) build() error {
|
|
if ctx.built {
|
|
return nil
|
|
}
|
|
|
|
// Initialize with proper generator coordinates
|
|
var generator GroupElementAffine
|
|
var gx, gy [32]byte
|
|
|
|
// Generator X coordinate
|
|
gx = [32]byte{
|
|
0x79, 0xBE, 0x66, 0x7E, 0xF9, 0xDC, 0xBB, 0xAC,
|
|
0x55, 0xA0, 0x62, 0x95, 0xCE, 0x87, 0x0B, 0x07,
|
|
0x02, 0x9B, 0xFC, 0xDB, 0x2D, 0xCE, 0x28, 0xD9,
|
|
0x59, 0xF2, 0x81, 0x5B, 0x16, 0xF8, 0x17, 0x98,
|
|
}
|
|
// Generator Y coordinate
|
|
gy = [32]byte{
|
|
0x48, 0x3A, 0xDA, 0x77, 0x26, 0xA3, 0xC4, 0x65,
|
|
0x5D, 0xA4, 0xFB, 0xFC, 0x0E, 0x11, 0x08, 0xA8,
|
|
0xFD, 0x17, 0xB4, 0x48, 0xA6, 0x85, 0x54, 0x19,
|
|
0x9C, 0x47, 0xD0, 0x8F, 0xFB, 0x10, 0xD4, 0xB8,
|
|
}
|
|
|
|
generator.x.setB32(gx[:])
|
|
generator.y.setB32(gy[:])
|
|
generator.x.normalize()
|
|
generator.y.normalize()
|
|
generator.infinity = false
|
|
|
|
// Build precomputed table for optimized generator multiplication
|
|
current := generator
|
|
|
|
// For each window position (64 windows of 4 bits each)
|
|
for i := 0; i < 64; i++ {
|
|
// First entry is point at infinity (0 * current)
|
|
ctx.prec[i][0] = InfinityAffine
|
|
|
|
// Remaining entries are multiples: 1*current, 2*current, ..., 15*current
|
|
ctx.prec[i][1] = current
|
|
|
|
var temp GroupElementJacobian
|
|
temp.setGE(¤t)
|
|
|
|
for j := 2; j < 16; j++ {
|
|
temp.addGE(&temp, ¤t)
|
|
ctx.prec[i][j].setGEJ(&temp)
|
|
}
|
|
|
|
// Move to next window: current = 2^4 * current = 16 * current
|
|
temp.setGE(¤t)
|
|
for k := 0; k < 4; k++ {
|
|
temp.double(&temp)
|
|
}
|
|
current.setGEJ(&temp)
|
|
}
|
|
|
|
// Initialize blinding point to infinity
|
|
ctx.blindPoint = InfinityAffine
|
|
ctx.built = true
|
|
return nil
|
|
}
|
|
|
|
// clear clears the ecmult_gen context
|
|
func (ctx *EcmultGenContext) clear() {
|
|
// Clear precomputed data
|
|
for i := range ctx.prec {
|
|
for j := range ctx.prec[i] {
|
|
ctx.prec[i][j].clear()
|
|
}
|
|
}
|
|
ctx.blindPoint.clear()
|
|
ctx.built = false
|
|
}
|
|
|
|
// blind applies blinding to the precomputed table for side-channel protection
|
|
func (ctx *EcmultGenContext) blind(seed32 []byte) error {
|
|
if !ctx.built {
|
|
return errors.New("context not built")
|
|
}
|
|
|
|
var blindingFactor Scalar
|
|
|
|
if seed32 == nil {
|
|
// Remove blinding
|
|
ctx.blindPoint = InfinityAffine
|
|
return nil
|
|
} else {
|
|
blindingFactor.setB32(seed32)
|
|
}
|
|
|
|
// Apply blinding to precomputed table
|
|
// This is a simplified implementation - real version needs proper blinding
|
|
|
|
// For now, just mark as blinded (actual blinding is complex)
|
|
return nil
|
|
}
|
|
|
|
// isBuilt returns true if the ecmult_gen context is built
|
|
func (ctx *EcmultGenContext) isBuilt() bool {
|
|
return ctx.built
|
|
}
|
|
|
|
// Selftest performs basic self-tests to detect serious usage errors
|
|
func Selftest() error {
|
|
// Test basic field operations
|
|
var a, b, c FieldElement
|
|
a.setInt(1)
|
|
b.setInt(2)
|
|
c.add(&a)
|
|
c.add(&b)
|
|
c.normalize()
|
|
|
|
var expected FieldElement
|
|
expected.setInt(3)
|
|
expected.normalize()
|
|
|
|
if !c.equal(&expected) {
|
|
return errors.New("field addition self-test failed")
|
|
}
|
|
|
|
// Test basic scalar operations
|
|
var sa, sb, sc Scalar
|
|
sa.setInt(2)
|
|
sb.setInt(3)
|
|
sc.mul(&sa, &sb)
|
|
|
|
var sexpected Scalar
|
|
sexpected.setInt(6)
|
|
|
|
if !sc.equal(&sexpected) {
|
|
return errors.New("scalar multiplication self-test failed")
|
|
}
|
|
|
|
// Test point operations
|
|
var p GroupElementAffine
|
|
p = GeneratorAffine
|
|
|
|
if !p.isValid() {
|
|
return errors.New("generator point validation failed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// declassifyMem marks memory as no-longer-secret for constant-time analysis
|
|
func (ctx *Context) declassifyMem(ptr unsafe.Pointer, len uintptr) {
|
|
if ctx.declassify {
|
|
// In a real implementation, this would call memory analysis tools
|
|
// For now, this is a no-op
|
|
}
|
|
}
|