Class: Secp256k1::Context

Inherits:
Object
  • Object
show all
Defined in:
lib/rbsecp256k1/context.rb,
ext/rbsecp256k1/rbsecp256k1.c

Overview

Wrapper around a secp256k1_context object.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ Secp256k1::Context

Initialize a new context.

Context initialization should be infrequent as it is an expensive operation.

Parameters:

  • context_randomization_bytes (String, nil)

    (Optional) 32 bytes of random data used to randomize the context. If omitted then the context remains unrandomized. It is recommended that you provide this argument.

Raises:



1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
# File 'ext/rbsecp256k1/rbsecp256k1.c', line 1480

static VALUE
Context_initialize(int argc, const VALUE* argv, VALUE self)
{
  Context *context;
  unsigned char *seed32;
  VALUE context_randomization_bytes;
  VALUE opts;
  static ID kwarg_ids;

  context_randomization_bytes = Qnil;
  if (!kwarg_ids)
  {
    CONST_ID(kwarg_ids, "context_randomization_bytes");
  }

  TypedData_Get_Struct(self, Context, &Context_DataType, context);

  context->ctx = secp256k1_context_create(
    SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY
  );

  // Handle optional second argument containing random bytes to use for
  // randomization. We pass ":" to rb_scan_args to say that we expect keyword
  // arguments. We then parse the opts result of the scan in order to grab
  // context_randomization_bytes from the hash.
  rb_scan_args(argc, argv, ":", &opts);
  rb_get_kwargs(opts, &kwarg_ids, 0, 1, &context_randomization_bytes);

  // We need this check because rb_get_kwargs will set the result to Qundef if
  // the keyword argument is not provided. This lets us use the NIL_P
  // predicate.
  if (context_randomization_bytes == Qundef)
  {
    context_randomization_bytes = Qnil;
  }

  if (!NIL_P(context_randomization_bytes)) // Random bytes given
  {
    Check_Type(context_randomization_bytes, T_STRING);
    if (RSTRING_LEN(context_randomization_bytes) != 32)
    {
      rb_raise(
        Secp256k1_Error_class,
        "context_randomization_bytes must be 32 bytes in length"
      );
    }

    seed32 = (unsigned char*)StringValuePtr(context_randomization_bytes);

    // Randomize the context at initialization time rather than before calls so
    // the same context can be used across threads safely.
    if (secp256k1_context_randomize(context->ctx, seed32) != 1)
    {
      rb_raise(
        Secp256k1_Error_class,
        "context randomization failed"
      );
    }
  }

  return self;
}

Class Method Details

.createSecp256k1::Context

Create a new randomized context.

Returns:



11
12
13
# File 'lib/rbsecp256k1/context.rb', line 11

def self.create
  new(context_randomization_bytes: SecureRandom.random_bytes(32))
end

.create_unrandomizedSecp256k1::Context

Create a new non-randomized context.

Returns:



18
19
20
# File 'lib/rbsecp256k1/context.rb', line 18

def self.create_unrandomized
  new
end

Instance Method Details

#ecdh(point, scalar) ⇒ Secp256k1::SharedSecret

Compute EC Diffie-Hellman secret in constant time.

Creates a new shared secret from public_key and private_key.

Parameters:

Returns:

Raises:



1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
# File 'ext/rbsecp256k1/rbsecp256k1.c', line 1837

static VALUE
Context_ecdh(VALUE self, VALUE point, VALUE scalar)
{
  Context *context;
  PublicKey *public_key;
  PrivateKey *private_key;
  SharedSecret *shared_secret;
  VALUE result;

  TypedData_Get_Struct(self, Context, &Context_DataType, context);
  TypedData_Get_Struct(point, PublicKey, &PublicKey_DataType, public_key);
  TypedData_Get_Struct(scalar, PrivateKey, &PrivateKey_DataType, private_key);

  result = SharedSecret_alloc(Secp256k1_SharedSecret_class);
  TypedData_Get_Struct(
    result, SharedSecret, &SharedSecret_DataType, shared_secret
  );

  if (secp256k1_ecdh(context->ctx,
                     shared_secret->data,
                     &(public_key->pubkey),
                     (unsigned char*)private_key->data,
                     NULL,
                     NULL) != 1)
  {
    rb_raise(Secp256k1_Error_class, "invalid scalar provided to ecdh");
    return Qnil;
  }

  rb_iv_set(result, "@data", rb_str_new((char*)shared_secret->data, 32));

  return result;
}

#generate_key_pairSecp256k1::KeyPair

Generates a new random key pair.

Returns:



25
26
27
# File 'lib/rbsecp256k1/context.rb', line 25

def generate_key_pair
  key_pair_from_private_key(SecureRandom.random_bytes(32))
end

#key_pair_from_private_key(in_private_key_data) ⇒ Secp256k1::KeyPair

Converts binary private key data into a new key pair.

Parameters:

  • in_private_key_data (String)

    binary private key data

Returns:

Raises:

  • (Secp256k1::Error)

    if the private key data is invalid or key derivation fails.



1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
# File 'ext/rbsecp256k1/rbsecp256k1.c', line 1551

static VALUE
Context_key_pair_from_private_key(VALUE self, VALUE in_private_key_data)
{
  Context *context;
  VALUE result;
  KeyPair *keypair;
  unsigned char *private_key_data;

  Check_Type(in_private_key_data, T_STRING);
  TypedData_Get_Struct(self, Context, &Context_DataType, context);

  if (RSTRING_LEN(in_private_key_data) != 32)
  {
    rb_raise(Secp256k1_Error_class, "private key data must be 32 bytes in length");
    return Qnil;
  }

  result = KeyPair_alloc(Secp256k1_KeyPair_class);
  TypedData_Get_Struct(result, KeyPair, &KeyPair_DataType, keypair);

  private_key_data = (unsigned char*)StringValuePtr(in_private_key_data);

  if (secp256k1_keypair_create(context->ctx, &keypair->keypair, private_key_data) == 0)
  {
    rb_raise(Secp256k1_Error_class, "invalid secret when attempting to create keypair");
  }

  return result;
}

#recoverable_signature_from_compact(in_compact_sig, in_recovery_id) ⇒ Secp256k1::RecoverableSignature

Loads recoverable signature from compact representation and recovery ID.

Parameters:

  • in_compact_sig (String)

    binary string containing compact signature data.

  • in_recovery_id (Integer)

    recovery ID (range [0, 3])

Returns:

Raises:



1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
# File 'ext/rbsecp256k1/rbsecp256k1.c', line 1771

static VALUE
Context_recoverable_signature_from_compact(
  VALUE self, VALUE in_compact_sig, VALUE in_recovery_id)
{
  Context *context;
  RecoverableSignature *recoverable_signature;
  unsigned char *compact_sig;
  int recovery_id;
  VALUE result;

  Check_Type(in_compact_sig, T_STRING);
  Check_Type(in_recovery_id, T_FIXNUM);
  TypedData_Get_Struct(self, Context, &Context_DataType, context);

  compact_sig = (unsigned char*)StringValuePtr(in_compact_sig);
  recovery_id = FIX2INT(in_recovery_id);

  if (RSTRING_LEN(in_compact_sig) != 64)
  {
    rb_raise(Secp256k1_Error_class, "compact signature is not 64 bytes");
    return Qnil;
  }

  if (recovery_id < 0 || recovery_id > 3)
  {
    rb_raise(Secp256k1_Error_class, "invalid recovery ID, must be in range [0, 3]");
    return Qnil;
  }

  result = RecoverableSignature_alloc(Secp256k1_RecoverableSignature_class);
  TypedData_Get_Struct(
    result,
    RecoverableSignature,
    &RecoverableSignature_DataType,
    recoverable_signature
  );

  if (secp256k1_ecdsa_recoverable_signature_parse_compact(
        context->ctx,
        &(recoverable_signature->sig),
        compact_sig,
        recovery_id) == 1)
  {
    recoverable_signature->ctx = secp256k1_context_clone(context->ctx);
    return result;
  }
  
  rb_raise(Secp256k1_DeserializationError_class, "unable to parse recoverable signature");
  return Qnil;
}

#sign(in_private_key, in_hash32) ⇒ Secp256k1::Signature

Computes the ECDSA signature of the data using the secp256k1 elliptic curve.

Parameters:

  • in_private_key (Secp256k1::PrivateKey)

    private key to use for signing.

  • in_hash32 (String)

    32-byte binary string with SHA-256 hash of data.

Returns:

Raises:

  • (Secp256k1::Error)

    if hash is not 32-bytes in length or signature computation fails.



1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
# File 'ext/rbsecp256k1/rbsecp256k1.c', line 1591

static VALUE
Context_sign(VALUE self, VALUE in_private_key, VALUE in_hash32)
{
  unsigned char *hash32;
  PrivateKey *private_key;
  Context *context;
  Signature *signature;
  VALUE signature_result;

  Check_Type(in_hash32, T_STRING);

  if (RSTRING_LEN(in_hash32) != 32)
  {
    rb_raise(Secp256k1_Error_class, "in_hash32 is not 32 bytes in length");
    return Qnil;
  }

  TypedData_Get_Struct(self, Context, &Context_DataType, context);
  TypedData_Get_Struct(in_private_key, PrivateKey, &PrivateKey_DataType, private_key);
  hash32 = (unsigned char*)StringValuePtr(in_hash32);

  signature_result = Signature_alloc(Secp256k1_Signature_class);
  TypedData_Get_Struct(signature_result, Signature, &Signature_DataType, signature);
 
  // Attempt to sign the hash of the given data
  if (SUCCESS(SignData(context->ctx,
                       hash32,
                       private_key->data,
                       &(signature->sig))))
  {
    return signature_result;
  }

  rb_raise(Secp256k1_Error_class, "unable to compute signature");
  return Qnil;
}

#sign_recoverable(in_private_key, in_hash32) ⇒ Secp256k1::RecoverableSignature

Computes the recoverable ECDSA signature of data signed with private key.

Parameters:

  • in_private_key (Secp256k1::PrivateKey)

    private key to sign with.

  • in_hash32 (String)

    32-byte binary string with SHA-256 hash of data.

Returns:

Raises:

  • (Secp256k1::Error)

    if the hash is not 32 bytes or signature could not be computed.



1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
# File 'ext/rbsecp256k1/rbsecp256k1.c', line 1716

static VALUE
Context_sign_recoverable(VALUE self, VALUE in_private_key, VALUE in_hash32)
{
  Context *context;
  PrivateKey *private_key;
  RecoverableSignature *recoverable_signature;
  unsigned char *hash32;
  VALUE result;

  Check_Type(in_hash32, T_STRING);
  if (RSTRING_LEN(in_hash32) != 32)
  {
    rb_raise(Secp256k1_Error_class, "in_hash32 is not 32 bytes in length");
    return Qnil;
  }

  TypedData_Get_Struct(self, Context, &Context_DataType, context);
  TypedData_Get_Struct(
    in_private_key, PrivateKey, &PrivateKey_DataType, private_key
  );
  hash32 = (unsigned char*)StringValuePtr(in_hash32);

  result = RecoverableSignature_alloc(Secp256k1_RecoverableSignature_class);
  TypedData_Get_Struct(
    result,
    RecoverableSignature,
    &RecoverableSignature_DataType,
    recoverable_signature
  );

  if (SUCCESS(RecoverableSignData(context->ctx,
                                  hash32,
                                  private_key->data,
                                  &(recoverable_signature->sig))))
  {
    recoverable_signature->ctx = secp256k1_context_clone(context->ctx);
    return result;
  }

  rb_raise(Secp256k1_Error_class, "unable to compute recoverable signature");
  return Qnil;
}

#sign_schnorr(keypair, message) ⇒ Secp256k1::SchnorrSignature

Create Schnorr signature generating auxrand.

Returns:



32
33
34
# File 'lib/rbsecp256k1/context.rb', line 32

def sign_schnorr(keypair, message)
  sign_schnorr_custom(keypair, message, SecureRandom.random_bytes(32))
end

#sign_schnorr_custom(in_keypair, in_message, in_auxrand) ⇒ Object



1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
# File 'ext/rbsecp256k1/rbsecp256k1.c', line 1875

static VALUE
Context_sign_schnorr_custom(VALUE self, VALUE in_keypair, VALUE in_message, VALUE in_auxrand)
{
  Context* context;
  KeyPair* keypair;
  SchnorrSignature* schnorr_sig;
  unsigned char* msg;
  unsigned char* auxrand;
  unsigned char sig[64];
  VALUE result;

  TypedData_Get_Struct(self, Context, &Context_DataType, context);
  TypedData_Get_Struct(in_keypair, KeyPair, &KeyPair_DataType, keypair);

  Check_Type(in_message, T_STRING);
  if (RSTRING_LEN(in_message) != 32)
  {
    rb_raise(Secp256k1_Error_class, "schnorr signing message must be 32-bytes in length");
    return Qnil;
  }

  if (!NIL_P(in_auxrand))
  {
    Check_Type(in_auxrand, T_STRING);
    if (RSTRING_LEN(in_auxrand) != 32)
    {
      rb_raise(Secp256k1_Error_class, "schnorr signing auxrand must be 32-bytes in length");
      return Qnil;
    }
  }

  msg = (unsigned char*)StringValuePtr(in_message);
  auxrand = (unsigned char*)StringValuePtr(in_auxrand);

  if (secp256k1_schnorrsig_sign32(context->ctx, sig, msg, &keypair->keypair, auxrand) != 1)
  {
    rb_raise(Secp256k1_Error_class, "schnorr signing failed");
    return Qnil;
  }

  result = SchnorrSignature_alloc(Secp256k1_SchnorrSignature_class);
  TypedData_Get_Struct(result, SchnorrSignature, &SchnorrSignature_DataType, schnorr_sig);
  memcpy(schnorr_sig->sig, sig, SCHNORR_SIG_SIZE_BYTES);

  return result;
}

#tagged_sha256(in_tag, in_message) ⇒ String

Computes the tagged hash as defined in BIP-340.

Parameters:

  • in_tag (String)

    tag value included in the hash.

  • in_message (String)

    message to be hashed.

Returns:

  • (String)

    32-byte binary hash.



1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
# File 'ext/rbsecp256k1/rbsecp256k1.c', line 1635

static VALUE
Context_tagged_sha256(VALUE self, VALUE in_tag, VALUE in_message)
{
  Context *context;
  unsigned char* tag;
  unsigned char* msg;
  unsigned char hash32[32];

  Check_Type(in_tag, T_STRING);
  Check_Type(in_message, T_STRING);

  TypedData_Get_Struct(self, Context, &Context_DataType, context);

  tag = (unsigned char*)StringValuePtr(in_tag);
  msg = (unsigned char*)StringValuePtr(in_message);

  if (secp256k1_tagged_sha256(context->ctx, hash32, tag, RSTRING_LEN(in_tag), msg, RSTRING_LEN(in_message)) != 1)
  {
    rb_raise(Secp256k1_Error_class, "failed to compute tagged SHA256");
    return Qnil;
  }

  return rb_str_new((char*)hash32, 32);
}

#verify(in_signature, in_pubkey, in_hash32) ⇒ Boolean

Verifies that signature matches public key and data.

Parameters:

  • in_signature (Secp256k1::Signature)

    signature to be verified.

  • in_pubkey (Secp256k1::PublicKey)

    public key to verify signature against.

  • in_hash32 (String)

    32-byte binary string containing SHA-256 hash of data.

Returns:

  • (Boolean)

    True if the signature is valid, false otherwise.

Raises:



1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
# File 'ext/rbsecp256k1/rbsecp256k1.c', line 1671

static VALUE
Context_verify(VALUE self, VALUE in_signature, VALUE in_pubkey, VALUE in_hash32)
{
  Context *context;
  PublicKey *public_key;
  Signature *signature;
  unsigned char *hash32;

  Check_Type(in_hash32, T_STRING);

  if (RSTRING_LEN(in_hash32) != 32)
  {
    rb_raise(Secp256k1_Error_class, "in_hash32 is not 32-bytes in length");
  }

  TypedData_Get_Struct(self, Context, &Context_DataType, context);
  TypedData_Get_Struct(in_pubkey, PublicKey, &PublicKey_DataType, public_key);
  TypedData_Get_Struct(in_signature, Signature, &Signature_DataType, signature);

  hash32 = (unsigned char*)StringValuePtr(in_hash32);
  
  if (secp256k1_ecdsa_verify(context->ctx,
                             &(signature->sig),
                             hash32,
                             &(public_key->pubkey)) == 1)
  {
    return Qtrue;
  }

  return Qfalse;
}